2016-08-01 15 views
0

私はクロージャのプロパティを持つpythonでカウンタを作成しようとしています。次の作品にコード:python counter with closure

def generate_counter(): 
    CNT = [0] 
    def add_one(): 
     CNT[0] = CNT[0] + 1 
     return CNT[0] 
    return add_one 

私はVARにリストCNTを変更した場合しかし、それは動作しませんでした:

def generate_counter1(): 
    x = 0 
    def add_one(): 
     x = x + 1 
     return x 
    return add_one 

私は、インスタンスの閉鎖プロパティを印刷するとき、私が見つけました

>>> ct1 = generate_counter() 
>>> ct2 = generate_counter1() 
>>> print(ct1.__closure__[0]) 
<cell at 0xb723765c: list object at 0xb724370c> 
>>> print(ct2.__closure__) 
None 

ちょうど外側の関数のインデックスをリストにする必要があり、なぜ疑問に思う:第二例__closure__はnoneですか?


お返事ありがとうございました!この問題をはっきりと説明したドキュメントを見つけました。 https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

+0

この複製のように見えます。http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope – ChatterOne

答えて

3

Pythonは名前バインディングの動作を調べて名前の有効範囲を判断します。代入は1つのそのような振る舞いです(関数のパラメータ、インポート、ターゲットはfor target ...またはwhile .. as target)。関数でバインドした名前は、ローカルとみなされます。リファレンスドキュメントのNaming and Binding sectionを参照してください。

あなたはそれに直接割り当てられているのでだからあなたの第二の例では名前xは、ローカル変数です:

実際に
x = x + 1 

、あなたがxそのローカル値を与えたことはないので、あなたが買ってあげますその関数を使用しようとすると例外が発生します。あなたはそれを読むしようとすると、ローカル名は結合していないです:

>>> generate_counter1()() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 4, in add_one 
UnboundLocalError: local variable 'x' referenced before assignment 

をあなたの最初の例では、そのような結合が行われません。代わりにの内容をCNTに変更していますが、その名前が参照する内容は変更されていません。

あなたは、Python 3を使用している場合、あなたはnonlocal statementを使用して、名前をローカルにする決定を無効にすることができます

def generate_counter2(): 
    x = 0 
    def add_one(): 
     nonlocal x 
     x = x + 1 
     return x 
    return add_one 

x非ローカルにすることによって、Pythonは親コンテキストでそれを発見し、作成もう一度閉鎖してください。

>>> def generate_counter2(): 
...  x = 0 
...  def add_one(): 
...   nonlocal x 
...   x = x + 1 
...   return x 
...  return add_one 
... 
>>> generate_counter2().__closure__ 
(<cell at 0x1078c62e8: int object at 0x1072c8070>,) 

nonlocalはPython 3の新機能です。 Python 2では、バインディングルールを回避するために変更可能なリストオブジェクトを使用するようなトリックに限定されています。別のトリックは、入れ子関数の属性にカウンタを割り当てることです。再び、これは現在のスコープに名前をバインド回避:

def generate_counter3(): 
    def add_one(): 
     add_one.x += 1 
     return add_one.x 
    add_one.x = 0 
    return add_one 
0

それはをリストするを持っていない、それはちょうどあなたが再割り当て、ではなく変異する変更可能なオブジェクトである必要があります。docsから

変数はどこでも、関数の本体内で値が割り当てられている場合、明示的にグローバルとして宣言しない限り、ローカルであるとみなされます。

このように、あなたの第二の例では、xは(内部関数の)ローカルとみなされ、そして、あなたの最初の代入の後、あなたは外「X」シャドーイングしています。

一方、最初の例では(CNTに値を割り当てないため)、外部関数で定義されたCNTで動作します。

+1

"不変オブジェクトでなければなりません"平均可変性? – roganjosh

+1

@roganjosh確かに私はやった!修正のおかげで:) – jedwards