外部ではあるがグローバルではないスコープにあるPythonの変数を変更することは可能ですか?


107

次のコードがあるとします:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

B()関数変数のコードbは外部スコープにありますが、グローバルスコープにはありません。関数b内から変数を変更することは可能B()ですか?確かに私はこことからそれを読むことができますprint()が、どのように変更するのですか?


すみません、もちろん2.7 :)。Python 3のスコープルールが変更されました。
grigoryvp

b変更可能である限りできます。への割り当てbは、外側のスコープをマスクします。
JimB 2011

4
これnonlocalは、2.xにバックポートされていないPythonの厄介な問題の1つです。これはクロージャサポートの本質的な部分です。
Glenn Maynard

回答:


96

Python 3.xにはnonlocalキーワードがあります。これはあなたが望んでいることだと思いますが、Python 2または3のどちらを実行しているかはわかりません。

非ローカルステートメントにより、リストされた識別子は、最も近い囲みスコープ内の以前にバインドされた変数を参照します。バインディングのデフォルトの動作は、最初にローカル名前空間を検索することなので、これは重要です。このステートメントを使用すると、カプセル化されたコードで、グローバル(モジュール)スコープのほかに、ローカルスコープ外の変数を再バインドできます。

Python 2の場合、私は通常、(リストやdictなどの)変更可能なオブジェクトを使用し、再割り当てする代わりに値を変更します。

例:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

出力:

[1, 1]

16
これを行う良い方法はclass nonlocal: pass、外側のスコープにあります。次にnonlocal.x、内部スコープでに割り当てることができます。
キンドール2014

1
今まで、私はすでにシンプルですが非常に役立つ2つのpythonのヒントを持っています:あなたの2番目のものです:)ありがとう@kindall!
swdev、2014

@kindallは素晴らしいハックです。Python3の構文と最低限異なり、可変オブジェクトを渡すよりもはるかに読みやすくなっています。
dimo414 2015

2
@kindallは非常にきちんとした感謝のヒープです:)前方互換性がなくなるため、おそらく別の名前が必要です。Python 3では、これはキーワードの競合であり、を引き起こしますSyntaxError。たぶんNonLocal
Adam Terrey 16

または、技術的にはクラスなので、Nonlocal?:-)
2016

19

空のクラスを使用して一時的なスコープを保持できます。それは変更可能ですが、少しきれいです。

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

これにより、次のインタラクティブな出力が生成されます。

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

これは、フィールドを含むクラスが「nonlocal」キーワードで外部変数を定義しない限り、内部関数では「可視」ですが、変数は可視でないことは奇妙です。
セルドール2018

12

私はPythonを少し始めましたが、これについて少し読んだことがあります。あなたが得る最高のものは、Javaの回避策に似ています。これは、外部変数をリストにラップすることです。

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

編集:これはおそらくPython 3の前に当てはまったnonlocalと思います。


4

いいえ、少なくともこの方法ではできません。

「set操作」は現在のスコープに新しい名前を作成し、外側の名前をカバーするためです。


「外側のものをカバーする」どういう意味ですか?入れ子関数で名前bのオブジェクトを定義しても、この関数の外部空間にある同じ名前のオブジェクトには影響しません
eyquem

1
@eyquemつまり、割り当てステートメントがどこにあっても、現在のスコープ全体に名前が導入されます。質問のサンプルコードなど、次の場合:def C():print(b)b = 2 "b = 2"は、C funcスコープ全体に名前bを導入するため、print(b)の場合、ローカルCのfuncスコープでbを取得しようとしますが、外部のスコープではありません。ローカルbはまだ初期化されていないため、エラーが発生します。
zchenah

1

より安全だが重い回避策でこれをずっと後で見ている人のためにです。変数をパラメーターとして渡す必要はありません。

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]

1

自動的に機能する短い答え

この特定の問題を解決するためにPythonライブラリを作成しました。unlisenceでリリースされているので、好きなように使ってください。でインストールするpip install seapieか、ホームページhttps://github.com/hirsimaki-markus/SEAPIEで確認できます

user@pc:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

出力

2

引数には次の意味があります。

  • 最初の引数は実行スコープです。0はローカルを意味しB()、1は親A()を意味し、2は祖父母または<module>グローバルを意味します
  • 2番目の引数は、指定されたスコープで実行する文字列またはコードオブジェクトです
  • プログラム内の対話型シェルの引数なしでそれを呼び出すこともできます

長い答え

これはもっと複雑です。Seapieは、CPython APIを使用してコールスタックのフレームを編集することで機能します。CPythonは事実上の標準であるため、ほとんどの人は心配する必要はありません。

あなたがこれを読んでいる場合、おそらくあなたが興味を持つであろう魔法の言葉は次のとおりです:

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

後者は、更新を強制的にローカルスコープに渡します。ただし、ローカルスコープはグローバルスコープとは異なる方法で最適化されるため、何らかの方法で初期化されていないと、新しいオブジェクトを直接呼び出すときに問題が発生します。これらの問題を回避するいくつかの方法をgithubページからコピーします

  • オブジェクトの割り当て、インポート、事前定義
  • 事前にオブジェクトにプレースホルダーを割り当てる
  • シンボルテーブルを更新するために、メインプログラムでオブジェクトをそれ自体に再度割り当てます:x = locals()["x"]
  • 最適化を回避するために、直接呼び出すのではなく、メインプログラムでexec()を使用します。xを呼び出す代わりに:exec( "x")

を使用しexec()たくない場合は、真のローカルディクショナリ(locals()によって返されるディクショナリではない)を更新することで、動作をエミュレートできます。https://faster-cpython.readthedocs.io/mutable.htmlから例をコピーします

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

出力:

hack!

0

私はあなたが考えていないはずですこれをやってみたいです。そのコンテキストが関数の知識なしに書かれるかもしれないので、そのコンテキストを囲むコンテキストで物事を変えることができる関数は危険です。

Bをクラスのパブリックメソッドにし、Cをプライベートメソッドにすることによって、それを明示的にすることができます(おそらく最良の方法)。または、リストなどの変更可能な型を使用して、明示的にCに渡します。

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

2
関数内にネストされた関数を知らなくても、関数をどのように書くことができますか?入れ子の関数とクロージャは、彼らがで囲まれている機能の本質的な部分である。
グレン・メイナード

あなたはあなたの中に含まれている関数のインターフェースについて知る必要がありますが、それらの中で何が起こっているのかを知る必要はありません。また、それらが呼び出す関数などで何が起こっているかを知ることは期待できません!関数が非グローバルメンバーまたは非クラスメンバーを変更する場合、通常はインターフェイスを介して明示的にする必要があります。つまり、パラメーターとして受け取ります。
サイドショーボブ

もちろん、Pythonはあなたにそれほど良いことを強制するわけではないので、nonlocalキーワードですが、慎重に使用するのはあなた次第です。
サイドショーボブ

5
@ボブ:このようなクロージャーを使用することは、言語の癖による以外は危険であるとは決して知りませんでした。ローカルは一時的なクラスであり、ローカル関数はクラスのメソッドであると考えてください。それよりも複雑ではありません。YMMVだと思います。
Glenn Maynard

0

できますが、グローバルステートメントを使用する必要があります(グローバル変数を使用するときはいつものように本当に良い解決策ではありませんが、機能します)。

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()

この解決策の潜在的な欠点を説明する私の答えを参照してください
eyquem

4
グローバル変数の使用は完全に異なります。
Glenn Maynard

0

__dict__この外部空間がグローバルスペースではない場合に、関数の外部空間を与える関数の属性があるかどうかはわかりません==モジュール、つまり、関数がネストされた関数である場合、 Python 3。

しかし、Python 2では、私の知る限り、そのような属性はありません。

だからあなたが望むことをする唯一の可能性は:

1)他の人が言うように、可変オブジェクトを使用する

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

結果

b before B() == 1
b == 10
b after B() == 10

ノタ

セドリックジュリアンの解決策には欠点があります。

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

結果

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

の実行後のグローバルbA()は変更されており、そのようにしないでください。

これは、グローバル名前空間に識別子bのオブジェクトがある場合にのみ当てはまります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.