【问题标题】:Does an equivalent of override exist for nested functions?嵌套函数是否存在等效的覆盖?
【发布时间】:2012-08-11 03:00:43
【问题描述】:

如果我有这个功能,我该怎么做才能将内部功能替换为我自己的自定义版本?

def foo():
    def bar():
        # I want to change this
        pass

    # here starts a long list of functions I want to keep unchanged
    def baz():
        pass

使用类很容易覆盖方法。不过,我不知道如何使用嵌套函数来做到这一点。将foo 更改为类(或其他任何东西)不是一种选择,因为它来自我无法修改的给定导入模块。

【问题讨论】:

  • bar 在哪里使用?它是否在以后的函数中使用(例如baz?)您想在这些情况下替换它,对吗?
  • 您可以访问函数的各种内部细节,因为它们只是对象。使用 dir 函数和语言参考来了解更多信息。
  • 我通过破解 func_code 让它工作更深一层,但必须让我的替换更聪明,以允许从 baz 调用 bar。从好的方面来说,我已经有一段时间没有看到SystemError: /Users/sysadmin/build/v2.7.2/Objects/cellobject.c:24: bad argument to internal function了!
  • 你有foo的来源吗?也许您可以编写自己的foo 版本,然后对其进行monkeypatch。
  • 我和@JoelCornett 在一起。为什么不能修改?可以复制吗? (如果它存储在 GitHub 之类的地方,你甚至可以分叉它)。

标签: python function nested overriding


【解决方案1】:

这是一种方法,通过破解函数内部结构来创建一个“做正确的事”的新 foo。 (如@DSM 所述)。不幸的是,我们不能直接跳入 foo 函数并弄乱它的内部结构,因为它们大多被标记为只读,所以我们要做的就是修改我们手动构建的副本。

# Here's the original function
def foo():
  def bar():
    print("    In bar orig")
  def baz():
    print("  Calling bar from baz")
    bar()
  print("Foo calling bar:")
  bar()
  print("Foo calling baz:")
  baz()

# Here's using it
foo()

# Now lets override the bar function

import types

# This is our replacement function
def my_bar():
  print("   Woo hoo I'm the bar override")

# This creates a new code object used by our new foo function 
# based on the old foo functions code object.
foocode = types.CodeType(
    foo.func_code.co_argcount,
    foo.func_code.co_nlocals,
    foo.func_code.co_stacksize,
    foo.func_code.co_flags,
    foo.func_code.co_code,
    # This tuple is a new version of foo.func_code.co_consts
    # NOTE: Don't get this wrong or you will crash python.
    ( 
       foo.func_code.co_consts[0],
       my_bar.func_code,
       foo.func_code.co_consts[2],
       foo.func_code.co_consts[3],
       foo.func_code.co_consts[4]
    ),
    foo.func_code.co_names,
    foo.func_code.co_varnames,
    foo.func_code.co_filename,
    foo.func_code.co_name,
    foo.func_code.co_firstlineno,
    foo.func_code.co_lnotab,
    foo.func_code.co_freevars,
    foo.func_code.co_cellvars )

# This is the new function we're replacing foo with
# using our new code.
foo = types.FunctionType( foocode , {})

# Now use it
foo()

我很确定它不会涵盖所有情况。但它适用于示例(对我来说是旧的 python 2.5.1)

可以做一些整理的丑陋的部分是:

  1. 正在传递给 CodeType 的巨大参数列表
  2. co_consts 构造的丑陋元组仅覆盖一个成员。所有信息都在 co_consts 中以确定替换哪个 - 所以更智能的函数可以做到这一点。我使用print( foo.func_code.co_consts ) 手动挖掘了内部结构。

您可以使用解释器找到有关CodeTypeFunctionType 的一些信息 命令help( types.CodeType )

更新: 我觉得这太丑了,所以我构建了一个辅助函数来让它更漂亮。使用助手,您可以编写:

# Use our function to get a new version of foo with "bar" replaced by mybar    
foo = monkey_patch_fn( foo, "bar", my_bar )

# Check it works
foo()

这里是monkey_patch_fn的实现:

# Returns a copy of original_fn with its internal function
# called name replaced with new_fn.
def monkey_patch_fn( original_fn, name, new_fn ):

  #Little helper function to pick out the correct constant
  def fix_consts(x):
    if x==None: return None
    try:
      if x.co_name == name:
        return new_fn.func_code
    except AttributeError, e:
        pass
    return x

  original_code = original_fn.func_code
  new_consts = tuple( map( fix_consts, original_code.co_consts ) )
  code_type_args = [
     "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code",
     "co_consts", "co_names", "co_varnames", "co_filename", "co_name",
     "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars" ]

  new_code = types.CodeType(
     *[ ( getattr(original_code,x) if x!="co_consts" else new_consts )
        for x in code_type_args ] )
  return types.FunctionType( new_code, {} )

【讨论】:

  • 对这个答案使用两种编码方法可以提供两倍的洞察力。
  • 可能值得注意的是,FunctionType 构造函数中的第二个参数表示全局变量,在本例中设置为空字典。在 2.7 中,我在调用修改后的函数时遇到了 int 未定义的问题;为了解决这个问题,我将{} 替换为globals=globals()
  • @ayanokouji 更好的解决方案是挑选出原始函数正在使用的全局变量。但我不知道该怎么做(自从我看到这个已经 8 年了......)如果你对如何做到这一点有任何想法,我很乐意更新它以正确处理它。
  • @MichaelAnderson 好点;我相信它可以通过 .func_globals 获得。 (也很抱歉挖了一个旧线程哈哈)
【解决方案2】:

您可以将其作为可选参数传入

def foo(bar=None):
    def _bar():
        # I want to change this
        pass
    if bar is None:
        bar = _bar

【讨论】:

  • 我不确定我是否理解。您的回答不是暗示要更改给定的功能吗?可能我还没有说清楚,但是我改不了原来的foo。顺便说一句,我要稍微修改一下我的问题。
  • 我认为 OP 正在寻找某种猴子补丁选项,因为 foo “来自我无法修改的给定导入模块。”
猜你喜欢
  • 2021-07-22
  • 1970-01-01
  • 2011-12-17
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 2015-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多