Python 2.6でクロージャーを実装しようとしていますが、非ローカル変数にアクセスする必要がありますが、このキーワードはpython 2。 x。これらのバージョンのPythonのクロージャで非ローカル変数にアクセスするにはどうすればよいですか?
内部関数はread 2.xの非ローカル変数であり、rebindではありません。これは迷惑ですが、回避することができます。辞書を作成し、データを要素として保存するだけです。内部関数はmutating非ローカル変数が参照するオブジェクトから禁止されていません。
ウィキペディアの例を使用するには:
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
次の解決策は 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)
辞書ではなく、非ローカルクラスの混乱が少なくなります。 @ChrisBの変更 例 :
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
assert f() == 4
各outer()呼び出しは、context(単に新しいインスタンスではない)と呼ばれる新しい別個のクラスを作成します。したがって、共有コンテキストに関する @ Nathaniel's beware を回避します。
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
ここで重要なのは、「アクセス」という意味です。クロージャの範囲外の変数の読み取りに問題はないはずです、例えば、
x = 3
def outer():
def inner():
print x
inner()
outer()
期待どおりに動作するはずです(印刷3)。ただし、xの値をオーバーライドしても機能しません。たとえば、
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
PEP-3104 の私の理解から、これはnonlocalキーワードがカバーするものです。 PEPで述べたように、クラスを使用して同じことを実行できます(厄介な種類):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
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.の答えのように、値は呼び出しごとに記憶されます。
アロイス・マーダルが別の answer に関して コメント で行った提案に触発されたものがあります:
class Nonlocal(object):
""" Helper to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nl = Nonlocal(y=0)
def inner():
nl.y += 1
return nl.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
更新
最近これを振り返ってみると、デコレータのように見えました。1つとして実装すると、より一般的で便利になります(ただし、読みやすさはある程度低下しますが)。
# Implemented as a decorator.
class Nonlocal(object):
""" Decorator class to help implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self._vars = kwargs
def __call__(self, func):
for k, v in self._vars.items():
setattr(func, k, v)
return func
@Nonlocal(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
両方のバージョンがPython 2および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
Pythonのスコープ規則にはいぼがあります-代入は、変数をそのすぐ外側の関数スコープに対してローカルにします。グローバル変数の場合、global
キーワードを使用してこれを解決します。
解決策は、2つのスコープ間で共有されるオブジェクトを導入することです。このオブジェクトには可変変数が含まれますが、それ自体は割り当てられていない変数を介して参照されます。
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コンビネーター。
上記のMartineauのエレガントなソリューションを、実用的でやや洗練されていないユースケースに拡張します。
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)