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


117

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

回答:


125

内部関数は 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

4
辞書から値を変更できるのはなぜですか?
coelhudo 2012

9
@coelhudo 非ローカル変数変更できるため。ただし、非ローカル変数への代入はできません。たとえば、UnboundLocalError:が発生しdef inner(): print d; d = {'y': 1}ます。ここでは、print d外部dを読み取りd、内部スコープで非ローカル変数を作成します。
スザンシャキャ

21
この回答をありがとう。ただし、用語を改善できると思います。「読み取り可能、変更不可」の代わりに、「参照可能、割り当て不可」の可能性があります。非ローカルスコープ内のオブジェクトの内容を変更することはできますが、参照されるオブジェクトを変更することはできません。
メタマット2013

3
または、辞書の代わりに任意のクラスを使用してオブジェクトをインスタンス化することもできます。コードがリテラル(辞書キー)で溢れず、メソッドを利用できるので、はるかにエレガントで読みやすくなっています。
Alois Mahdal 2013

6
Pythonの場合、動詞「bind」または「rebind」を使用し、「variable」ではなく名詞「name」を使用するのが最善だと思います。Python 2.xでは、オブジェクトを閉じることはできますが、元のスコープで名前を再バインドすることはできません。Cのような言語では、変数を宣言すると一部のストレージ(静的ストレージまたはスタック上の一時ストレージ)が予約されます。Pythonでは、式X = 1は名前Xを特定のオブジェクト(int値を持つ1)にバインドするだけです。 X = 1; Y = X2つの名前をまったく同じオブジェクトにバインドします。とにかく、一部のオブジェクトは変更可能であり、その値を変更できます。
steveha 2013

37

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

それは醜いものである必要はありません。inner.yを使用する代わりに、outer.yを使用します。def innerの前にouter.y = 0を定義できます。
jgomo3 2013

1
わかりました、コメントが間違っているようです。エリアス・ザマリアはouter.yソリューションも実装しました。しかし、Nathaniel [1]によってコメントされているように、注意する必要があります。この答えは解決策として推奨されるべきだと思いますが、outer.yの解決策とお守りに注意してください。[1] stackoverflow.com/questions/3190706/...
jgomo3

非ローカルな可変状態を共有する複数の内部関数が必要な場合、これは醜くなります。言うinc()dec()共有カウンタをインクリメントとデクリメントすることを外から戻りました。次に、現在のカウンター値をアタッチする関数を決定し、他の関数からその関数を参照する必要があります。少し奇妙で非対称に見えます。例えばのdec()ような行でinc.value -= 1
BlackJack 2016

33

辞書ではなく、非ローカルクラスを整理します。@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()呼び出しは、コンテキスト(新しいインスタンスだけでなく)と呼ばれる新しくて異なるクラスを作成します。そのため、共有コンテキストに関する@Nathanielの注意を回避できます。

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

assert f() == 5

いいね!これは確かに辞書よりエレガントで読みやすいです。
Vincenzo

ここでは一般的にスロットを使用することをお勧めします。タイプミスからあなたを守ります。
DerWeh、2018

@DerWeh興味深い、私はそのことを聞いことがありませ。どのクラスでも同じことが言えますか?タイプミスによるフラモックスが嫌いです。
ボブスタイン

@BobStein申し訳ありません、私はあなたが何を言っているのか本当に理解できません。しかし__slots__ = ()、クラスを使用する代わりにオブジェクトを追加して作成することで、たとえばcontext.z = 3を発生させAttributeErrorます。スロットを定義していないクラスから継承しない限り、すべてのクラスで可能です。
DerWeh、2018

@DerWeh タイプミスを見つけるためにスロットを使用することは聞いたことがありません。そうだね、それはクラス変数ではなくクラスインスタンスでのみ役立つだろう。たぶん、あなたはpyfiddleで実用的な例を作成したいと思います。少なくとも2行追加する必要があります。乱雑にせずに、どうやってそれを行うか想像できません。
ボブスタイン

14

ここで鍵となるのは、「アクセス」という意味です。クロージャスコープ外の変数の読み取りに問題はありません。たとえば、

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についての私の理解から、これはnonlocalキーワードがカバーするものです。PEPで述べたように、クラスを使用して同じことを実行できます(厄介な種類)。

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

クラスを作成してインスタンス化する代わりに、単純に関数を作成し、def ns(): passその後にを続けns.x = 3ます。それはきれいではありませんが、私の目には少し醜くありません。
davidchambers、2011年

2
反対票の特別な理由はありますか?私は私が最もエレガントな解決策ではないことを認めますが、それは機能します...
ig0774 2013年

2
どうclass Namespace: x = 3ですか?
Feuermurmel 2014

3
クロージャー内の参照ではなくグローバル参照を作成するため、この方法は非ローカルをシミュレートするとは思わない。
CarmeloS 2014年

1
私は@CarmeloSに同意します。これは、PEP-3104がPython 2で同様のことを達成する方法として提案することを実行しない最後のコードブロックです。nsこれがグローバルオブジェクトなので、最後のステートメントのns.xモジュールレベルで参照できます。print
マルティノー

13

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の回答と同じように、呼び出しごとに記憶されます。


19
注意してください:この方法で実装した場合f = outer()、後で実行するとg = outer()fのカウンターがリセットされます。これは、それぞれが独自の変数を持っているのではなく、両方が単一の outer.y変数を共有しているためです。このコードは、Chris Bの答えよりも見た目が美しく見えますが、outer複数回呼び出したい場合は、彼の方法が字句スコープをエミュレートする唯一の方法のようです。
Nathaniel

@Nathaniel:私がこれを正しく理解しているか見てみましょう。への割り当てにouter.yは、関数呼び出し(インスタンス)outer()にローカルなものは何も含まれませんがouter、それを含むスコープ内の名前にバインドされている関数オブジェクトの属性に割り当てられます。したがって、1も等しく書面で、使用している可能性がありouter.y他の代わりの名前をouter、その範囲にバインドすることが知られている提供。これは正しいです?
Marc van Leeuwen、2013

1
修正、私は「そのスコープにバインドされた」後に:型が属性(関数やクラスインスタンスなど)を設定できるオブジェクトに。また、このスコープは実際に必要なスコープよりも遠いので、次の非常に醜い解決策を示唆しているのではないでしょうか。outer.y名前を使用する代わりにinner.yinner呼び出し内にバインドされているためouter()、まさに目的のスコープです)、innerの定義inner.y = 0 の初期化(オブジェクトは、その属性が作成されるときに存在する必要があるため)ですが、もちろん?return inner
Marc van Leeuwen、2013

@MarcvanLeeuwenコメントがエリアスのinner.yによる解決策に影響を与えたようです。あなたの良い考え。
Ankur Agarwal

グローバル変数にアクセスして更新することは、おそらくクロージャのキャプチャを変更するときにほとんどの人がしようとしていることではありません。
binki

12

これは、アロイス・マダルが別の回答に関するコメントで行った提案に触発されたものです。

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の両方で機能することに注意してください。


これは、可読性と語彙スコープの維持の点で最高のようです。
アーロンS.カーランド2014年

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コンビネータを使用する必要があります。


この特定のケースでは両方のバージョンが機能しますが、の代わりにはなりませんnonlocal。が定義されているときにローカルのlocals()ディクショナリを作成しますが、そのディクショナリを変更してもin 変更されませ。これは、closed over変数を共有したい内部関数が多い場合には機能しなくなります。a と言って、共有カウンターをインクリメントおよびデクリメントします。outer()inner()vouter()inc()dec()
BlackJack 2016

@BlackJack nonlocalはpython 3の機能です。
Marcin 2016

はい、知っています。問題は、一般的なnonlocal Python 2 Python 3の効果をどのように実現するかでした。あなたのアイデアは、一般的なケースではなく、1 つの内部関数を持つものだけをカバーしています。例として、この要点ご覧ください。両方の内部関数には独自のコンテナーがあります。他の回答がすでに示唆しているように、外側の関数のスコープに変更可能なオブジェクトが必要です。
BlackJack 2016

@BlackJackそれは問題ではありませんが、あなたの入力に感謝します。
Marcin

はい、それ問題です。ページの上部をご覧ください。adinsaは、Python 2.6を持っているとすることなく、閉鎖に非ローカル変数にアクセスする必要があるnonlocalのPython 3で導入されたキーワード
ブラックジャック

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
実際、私は辞書を使用した解決策を好みますが、これはクールです:)
Ezer Fernandes '10

0

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

-3

グローバル変数を使用する

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。私の改善により、ユーザーから関数変数を初期化する必要がなくなりました。


辞書を使う方がずっといいです。でインスタンスを参照できますがinner、割り当てることはできませんが、キーと値を変更できます。これにより、グローバル変数の使用が回避されます。
johannestaas 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.