Python 2.6 でクロージャを実装しようとしていて、非ローカル変数にアクセスする必要がありますが、このキーワードは Python 2.x では使用できないようです。これらのバージョンの Python では、クロージャの非ローカル変数にどのようにアクセスすればよいですか?
10 に答える
2.x では、内部関数は非ローカル変数を読み取ることができますが、それらを再バインドすることはできません。これは面倒ですが、回避できます。ディクショナリを作成し、データを要素として格納するだけです。内部関数は、非ローカル変数が参照するオブジェクトを変更することを禁止されていません。
ウィキペディアの例を使用するには:
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
辞書ではなく、非ローカル クラスの方が煩雑ではありません。@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 の注意を回避します。
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
ここで重要なのは、「アクセス」の意味だと思います。クロージャー スコープ外の変数を読み取ることに問題はないはずです。
x = 3
def outer():
def inner():
print x
inner()
outer()
期待どおりに動作するはずです (printing 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.の回答のように、ある呼び出しから別の呼び出しまで記憶されます。
これは、Alois Mahdal が別の回答に関するコメントで行った提案に触発されたものです。
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)
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 コンビネータを使用する必要があります。
上記のマルティノーのエレガントなソリューションを実用的でややエレガントではないユースケースに拡張すると、次のようになります。
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)
グローバル変数を使用する
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
。私の改善により、ユーザーが関数変数を初期化する必要がなくなりました。