嵌套函数是否存在等价的覆盖?

时间:2012-08-11 03:00:43

标签: python function nested override

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

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更改为类(或其他任何内容)不是一个选项,因为它来自我无法修改的给定导入模块。

2 个答案:

答案 0 :(得分:10)

这是一种方法,通过破解函数内部创建一个“做正确的事”的新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 )手动挖掘内部构件。
  3. 您可以使用解释器找到有关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, {} )
    

答案 1 :(得分:3)

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

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