2010-07-06 56 views
93

我想在Python 2.6中實現一個閉包,我需要訪問一個非本地變量,但它似乎像這個關鍵字在python 2.x中不可用。在這些版本的python中,應該如何訪問非本地變量?在Python 2.x中的非本地關鍵字

回答

96

內函數可以 2.x中的非局部變量,只是不是rebind他們。這很煩人,但你可以解決它。只需創建一個字典,並將數據作爲元素存儲在其中。 不會禁止內部函數非本地變量引用的對象。

從維基百科使用例如:

def outer(): 
    d = {'y' : 0} 
    def inner(): 
     d['y'] += 1 
     return d['y'] 
    return inner 

f = outer() 
print(f(), f(), f()) #prints 1 2 3 
+3

爲什麼有可能修改字典中的值? – coelhudo 2012-04-13 02:29:46

+9

@ coelhudo因爲你**可以修改非本地變量。但是你不能對非本地變量進行賦值。例如: 例如,這會引起UnboundLocalError:'def inner():print d; d = {'y':1}'。在這裏,'print d'讀取外部'd',從而在內部範圍內創建非局部變量'd'。 – suzanshakya 2012-05-01 17:52:58

+18

感謝您的回答。儘管如此,我認爲你可以改進術語:而不是「可以閱讀,不能改變」,也許「可以參考,不能分配」。您可以更改非本地範圍中對象的內容,但不能更改引用的對象。 – metamatt 2013-02-04 06:46:42

12

我認爲,這裏的關鍵是你所說的「訪問」。應該沒有問題與讀取可變封閉範圍之外,例如,

x = 3 
def outer(): 
    def inner(): 
     print x 
    inner() 
outer() 

應該按預期工作(打印3)。然而,覆蓋x的值不起作用,例如,

x = 3 
def outer(): 
    def inner(): 
     x = 5 
    inner() 
outer() 
print x 

仍然會打印3.從我的PEP-3104這種認識是什麼外地關鍵字應該涵蓋。正如PEP提到的,你可以使用一個類來完成同樣的事情(有點雜亂):

class Namespace(object): pass 
ns = Namespace() 
ns.x = 3 
def outer(): 
    def inner(): 
     ns.x = 5 
    inner() 
outer() 
print ns.x 
+0

可以簡單地創建一個函數:'def ns():pass',然後創建一個類並實例化它由'ns.x = 3'。它不漂亮,但我的眼睛稍微不那麼難看。 – davidchambers 2011-01-25 11:36:26

+2

downvote的任何特定原因?我承認我不是最優雅的解決方案,但它的工作原理... – ig0774 2013-03-13 12:25:30

+0

第二個代碼示例不起作用,因爲對當前範圍中不存在的名稱(變量)的賦值將導致創建本地變量與分配的值。對?我不會找到其他合乎邏輯的行爲。 – 2013-07-01 13:48:31

2

在Python的作用域規則疣 - 分配使得局部變量它直接包含的功能範圍。對於全局變量,您可以使用global關鍵字來解決此問題。

解決方法是引入一個對象,該對象在兩個作用域之間共享,其中包含可變變量,但它本身是通過未分配的變量引用的。

def outer(v): 
    def inner(container = [v]): 
     container[0] += 1 
     return container[0] 
    return inner 

另一種是一些範圍兩輪牛車:

def outer(v): 
    def inner(varname = 'v', scope = locals()): 
     scope[varname] += 1 
     return scope[varname] 
    return inner 

您可能能夠找出一些掛羊頭賣狗肉,以獲取參數的名稱outer,然後把它作爲varname的,但不依賴在名稱outer上,您需要使用Y組合器。

+0

這兩個版本都適用於這種特殊情況,但不能替代'nonlocal'。 'locals()'在定義'inner()'的時候創建了一個'outer()'的本地代碼字典,但改變這個字典不會改變'outer()'中的'v'。當你有更多的內部函數想要共享一個關閉的變量時,這將不再起作用。說一個'inc()'和'dec()'遞增和遞減共享計數器。 – BlackJack 2016-09-14 07:53:31

+0

@BlackJack'nonlocal'是一個python 3特性。 – Marcin 2016-09-14 13:42:51

+0

是的,我知道。問題是如何在Python 2中實現Python 3的'nonlocal'效果_一般_。你的想法不包括一般情況,而只是一個具有內部功能的情況。看看[這個要點](https://gist.github.com/Marrin/e00fa440a313e5eb4ee7b1cc857797a4)爲例。兩個內部函數都有自己的容器。您需要在外部函數範圍內有一個可變對象,因爲其他答案已經提出。 – BlackJack 2016-09-14 15:31:50

9

還有就是要實現在Python 2非本地變量的另一種方式,萬一有什麼問題的答案在這裏是不受歡迎無論出於何種原因:

def outer(): 
    outer.y = 0 
    def inner(): 
     outer.y += 1 
     return outer.y 
    return inner 

f = outer() 
print(f(), f(), f()) #prints 1 2 3 

它是多餘的賦值語句中使用函數的名稱的變量,但它看起來比將變量放在字典中更簡單和更清晰。從一個電話到另一個電話,這個價值就會被記住,就像Chris B.的答案一樣。

+0

最清晰。 – 2013-01-17 17:52:51

+17

請注意:當以這種方式實現時,如果你執行'f = outer()',然後再執行'g = outer()',那麼'f'的計數器將被重置。這是因爲它們共享一個*​​ single *'outer.y'變量,而不是每個都有自己的獨立變量。儘管這段代碼看起來比Chris B的回答更美觀,但他的方式似乎是模擬詞法範圍的唯一方式,如果你想多次調用'outer'。 – Nathaniel 2013-03-14 03:57:51

+0

@Nathaniel:讓我看看我是否正確理解這一點。對'outer.y'的賦值不涉及函數調用(實例)'outer()'的任何局部變量,而是賦給函數對象的一個​​屬性,該屬性在_enclosing_範圍內綁定到名稱'outer'。因此,如果已知在該範圍內綁定,則可以同時使用'outer.y',_any other_ name而不是'outer'。它是否正確? – 2013-04-16 08:13:44

32

以下解決方案受answer by Elias Zamaria啓發,但與該答案相反,它正確地處理外部函數的多個調用。 「變量」inner.y位於當前呼叫outer的本地。只有它不是一個變量,因爲這是被禁止的,而是一個對象屬性(該對象本身就是函數inner)。這非常難看(注意只有在定義了inner函數後才能創建該屬性),但似乎有效。

def outer(): 
    def inner(): 
     inner.y += 1 
     return inner.y 
    inner.y = 0 
    return inner 

f = outer() 
g = outer() 
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2) 
+0

它不是很醜。而不是使用inner.y,使用outer.y。你可以在def內部之前定義outer.y = 0。 – jgomo3 2013-12-27 14:33:19

+1

好的,我看到我的評論是錯誤的。 Elias Zamaria也實施了outer.y解決方案。但正如納撒尼爾[1]評論的那樣,你應該小心。我認爲這個答案應該作爲解決方案來推廣,但是請注意關於outer.y解決方案和警告。 [1] http://stackoverflow.com/questions/3190706/nonlocal-keyword-in-python-2-x#comment21772934_13794589 – jgomo3 2013-12-27 14:44:54

+0

如果你想要多個內部函數共享非局部可變狀態,這會變得很難看。假設從外部返回的inc()和dec()從一個共享計數器遞增和遞減。然後,您必須決定將當前計數器值附加到哪個函數並從另一個函數引用該函數。這看起來有點奇怪和不對稱。例如。在'dec()'中,像'inc.value - = 1'這樣的一行。 – BlackJack 2016-09-14 08:03:15

-2

使用全局變量

def outer(): 
    global y # import1 
    y = 0 
    def inner(): 
     global y # import2 - requires import1 
     y += 1 
     return y 
    return inner 

f = outer() 
print(f(), f(), f()) #prints 1 2 3 

就個人而言,我不喜歡這個全局變量。但是,我的建議是基於https://stackoverflow.com/a/19877437/1083704答案

def report(): 
     class Rank: 
      def __init__(self): 
       report.ranks += 1 
     rank = Rank() 
report.ranks = 0 
report() 

其中用戶需要聲明一個全局變量ranks,每次你需要調用report時間。我的改進消除了從用戶初始化函數變量的需要。

+0

使用字典會好得多。你可以在'inner'中引用實例,但不能分配給它,但你可以修改它的鍵和值。這避免了全局變量的使用。 – johannestaas 2014-02-07 22:43:03

3

另一種方式來做到這一點(雖然它太冗長):

import ctypes 

def outer(): 
    y = 0 
    def inner(): 
     ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1)) 
     return y 
    return inner 

x = outer() 
x() 
>> 1 
x() 
>> 2 
y = outer() 
y() 
>> 1 
x() 
>> 3 
+1

其實我更喜歡使用字典的解決方案,但這個很酷:) – 2014-01-10 16:39:50

6

這裏的東西被建議阿洛伊斯Mahdal在comment作出關於另一answer啓發:

class Nonlocals(object): 
    """ Helper class to implement nonlocal names in Python 2.x """ 
    def __init__(self, **kwargs): 
     self.__dict__.update(kwargs) 

def outer(): 
    nonlocals = Nonlocals(y=0) 
    def inner(): 
     nonlocals.y += 1 
     return nonlocals.y 
    return inner 

f = outer() 
print(f(), f(), f()) # -> (1 2 3) 
+0

這一個似乎是最好的可讀性和保留詞彙範圍。 – 2014-10-08 03:17:20

0

擴展馬蒂諾優雅的解決方案以上,我得到一個實用的,不太優雅的用例:

class nonlocals(object): 
""" Helper to implement nonlocal names in Python 2.x. 
Usage example: 
def outer(): 
    nl = nonlocals(n=0, m=1) 
    def inner(): 
     nl.n += 1 
    inner() # will increment nl.n 

or... 
    sums = nonlocals({ k:v for k,v in locals().iteritems() if k.startswith('tot_') }) 
""" 
def __init__(self, **kwargs): 
    self.__dict__.update(kwargs) 

def __init__(self, a_dict): 
    self.__dict__.update(a_dict) 
23

而不是字典,非本地類雜亂。修改@ ChrisB的example

def outer(): 
    class context: 
     y = 0 
    def inner(): 
     context.y += 1 
     return context.y 
    return inner 

然後

f = outer() 
assert f() == 1 
assert f() == 2 
assert f() == 3 

每個外()調用創建一個單獨的上下文。所以它避免了@Nathaniel's beware

g = outer() 
assert g() == 1 
assert g() == 2 

assert f() == 4 
+0

不錯!這確實比字典更優雅和可讀。 – Vincenzo 2015-12-15 19:17:48