2012-01-11 64 views
2

我知道在使用Groovy閉包時,我可以在閉包上更改delegate,因此可以在閉包中定義函數調用。Python關閉和替換周邊範圍

我可以在Python中做類似的事嗎?

特別是,如果你把下面的代碼:

def configure(): 
    build() 

def wrap(function): 
    def build(): 
    print 'build' 

    function() 

wrap(configure) 

我想它來打印「構建」(只更改了wrap())。

一些注意事項:

我不想通過功能集成到configure()因爲可能有大量的可以由configure()被調用的函數。

我也不想全局定義它們,因爲可能會有大量的函數可以調用configure(),我不想污染全局名稱空間。

回答

5

是否是一個好方法來做到這一點是有爭議的,但是這裏有一個不修改全局名稱空間的解決方案。

def configure(): 
    build() 

def wrap(f): 
    import new 
    def build(): 
    print 'build' 

    new.function(f.func_code, locals(), f.func_name, f.func_defaults, f.func_closure)() 

wrap(configure) 

我發現它在How to modify the local namespace in python

+0

事實上 - 這是所問問題的確切答案。 'new.function'類型也可以作爲'types.FunctionType'使用,它的作用是使'wrap'中的局部變量作爲新創建函數中的全局變量。 – jsbueno 2012-01-11 22:30:09

3

沒有元編程,這是可行的。有configure採取build函數作爲參數:

def default_build(): 
    print "default build" 

def configure(build_func=None): 
    build_func = build_func or default_build 
    build_func() 

def wrap(func): 
    def build(): 
     print "wrap build" 

    func(build) 

wrap(configure) 

這樣使得它明確了configure函數的行爲可以改變。

您可以configure認爲還有做更多的東西一樣的命名空間撥弄我的理解Groovy的作用:

def build(): 
    print "default build" 

def configure(): 
    build() 

def wrap(func): 
    def _build(): 
     print "wrap build" 

    old_build = func.func_globals['build'] 
    func.func_globals['build'] = _build 
    func() 
    func.func_globals['build'] = old_build 
+0

當然可以,但讓我們說有很多功能可以稱之爲潛力。我寧願不對configure()進行更改。 – 2012-01-11 20:41:19

+1

@ReverendGonzo:我編輯了答案,添加了一個更直接的方法。但我仍然認爲這會導致代碼混淆。如果一個函數的行爲可以被參數化,那麼即使它很費力,我也會明確地表明這個事實。 – millimoose 2012-01-11 20:47:29

+0

@ReverendGonzo:實際上,我可能要做的就是應用OO並製作一個類的'configure'和'build'方法,並通過繼承類/ monkeypatching類的實例來改變它們。 – millimoose 2012-01-11 20:51:13

0

一種方式做這將是宣佈build全球在wrap

def configure(): 
    build() 

def wrap(function): 
    global build 
    def build(): 
    print 'build' 

    function() 

wrap(configure) 

但是,我不真的推薦這個,因爲它會污染命名空間。

+0

對,我不想污染命名空間。我只是想在調用configure時修改範圍。 – 2012-01-11 20:42:26

0

您需要使用global語句,以便build()在全局範圍內定義的,請嘗試以下操作:

def wrap(function): 
    global build 
    def build(): 
    print 'build' 

    function() 
+0

我不希望build()在全局範圍內,因爲這會破壞包裝configure()的作用域的目的。 – 2012-01-11 20:42:58

+0

您可以在'function()'行之後添加'del build'以從全局命名空間中移除'build()'。這是我知道讓你的代碼在不修改'configure()'的情況下工作的唯一方法。 – 2012-01-11 20:46:09

2

如果你感到瘋狂,真棒,看看this article about dynamic scoping

基本上,這個想法是修改一個函數的字節碼(使用byteplay模塊),並將所有不嚴格局部範圍的引用替換爲那些。爲了說明基本概念(在Python的僞代碼):

code = byteplay.extractcode(function) 
newbytecode = [] 

for opcode, arg in code.code: 
    if opcode in (NONLOCAL_CODES): 
     opcode = LOCAL_EQUIVALENT 

    newbytecode.append((opcode, arg)) 

code.code = newbytecode 

return code.to_code() 

這是稍微複雜多了,但文章提供了一些偉大的信息。

他還建議不要在生產中使用它。 :D