2012-08-11 60 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