2012-08-11 59 views
7

如果我有这个功能,我该怎么做才能用我自己定制的版本替换内部函数?嵌套函数是否存在等价的覆盖?

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

+0

当使用'bar'?它是否在后面的函数中使用(比如'baz'?)在这种情况下,你会*想要替换它,对吧?确切地说, – 2012-08-11 03:21:36

+0

。任何方式来做到这一点? – Paolo 2012-08-11 03:24:38

+0

您可以访问函数的各种内部细节,因为它们只是对象。使用dir函数和语言参考了解更多信息。 – Marcin 2012-08-11 03:48:31

回答

10

这里做的一种方式,创造了新富由黑客函数内部是“做正确的事”。 (正如@DSM所提到的)。不幸的是,我们无法跳入foo函数,并且因为它们的内部大部分被标记为只读,所以我们必须做的是修改我们手工构建的副本。

​​

我很确定它不会去捕捉所有情况。但是,它的工作原理的例子(我在一个旧的Python 2.5.1)

丑女位,可能与一些整理是做:

  1. 巨大的参数列表传递给CODETYPE
  2. 的从co_consts构建的丑陋元组只覆盖一个成员。所有的信息都在co_consts中以确定要替换哪个 - 所以更聪明的功能可以做到这一点。我使用print(foo.func_code.co_consts)手工挖入内部。

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

更新: 我认为这太丑了,所以我建立了一个辅助函数,使它更漂亮。有了助手,你可以写:

# 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, {}) 
+0

将这两种编码方法应用于此答案都提供了两倍的洞察力。 – MikeiLL 2014-09-10 17:50:01

3

你可以把它作为一个可选的参数

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

我不知道我明白。你的答案是否意味着改变给定的功能?也许我不清楚,但我不能改变原来的'foo'。顺便说一句,我要编辑我的问题了一下。 – Paolo 2012-08-11 03:15:55

+4

我认为OP正在寻找某种monkeypatch选项,因为'foo'“来自我无法修改的给定导入模块。” – PaulMcG 2012-08-11 03:17:50