web-dev-qa-db-ja.com

Python 2.xの非ローカルキーワード

Python 2.6でクロージャーを実装しようとしていますが、非ローカル変数にアクセスする必要がありますが、このキーワードはpython 2。 x。これらのバージョンのPythonのクロージャで非ローカル変数にアクセスするにはどうすればよいですか?

110
adinsa

内部関数は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
116
Chris B.

次の解決策は 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)
37

辞書ではなく、非ローカルクラスの混乱が少なくなります。 @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
29
Bob Stein

ここで重要なのは、「アクセス」という意味です。クロージャの範囲外の変数の読み取りに問題はないはずです、例えば、

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
14
ig0774

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.の答えのように、値は呼び出しごとに記憶されます。

12
Elias Zamaria

アロイス・マーダルが別の 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。

11
martineau

それを行う別の方法(冗長すぎます):

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
3
Ezer Fernandes

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コンビネーター。

3
Marcin

上記の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)
0
Amnon Harel