8

私はこのコードを書きました:

x = 0
def counter():
 x = 1
 def temp(self):
  print x
  x += 1
 return temp

Python がレキシカル スコープかダイナミック スコープかをテストしようとしています。私の考えはそうでした

y = counter()
y()

0 または 1 を出力する必要があり、Python がどのようにスコープされているかがわかります。ただし、y を呼び出すと、x が未定義であるという例外がスローされます。Python がどのように機能するかについての私の理解には、何か根本的な欠陥があるようです。

誰かがこれがどのように機能するか説明できますか? はい、これはオブジェクトを使用して簡単に実行できることを認識しています。オブジェクトを使用せずに関数に状態を与えるというアイデアを探求しようとしています。私がこのようにコードを書いたのは、Scheme のようなレキシカル スコープの言語に変換された上記が確実に機能するためです。

4

5 に答える 5

10

ドキュメントから:

Python の特別な癖は、グローバル ステートメントが有効でない場合、名前への代入が常に最も内側のスコープに入るということです。割り当てはデータをコピーしません — 名前をオブジェクトにバインドするだけです。

したがって、Pythonが解析するとき

 def temp(self):
  print x
  x += 1

割り当てを見て、最も内側のスコープにある必要があるとx += 1判断します。x後で-- (ちなみに、 の定義から を省略するか、temp(...)引数を1つ指定する必要があります) -- Python はステートメントに遭遇し、ローカル (最も内側) のスコープで定義されていないものを見つけます。 . したがって、エラー、y()selftempy()print xx

UnboundLocalError: local variable 'x' referenced before assignment

宣言すれば

 def temp(self):
     global x

次に、Python はxグローバル スコープ ( where x=0) で検索します。xPython2では、拡張スコープ ( where ) で探すように Python に指示する方法はありませんx=1。しかし、宣言で達成できるPython3では

 def temp(self):
     nonlocal x
于 2011-05-29T23:32:05.697 に答える
6

Python には 2 つのスコープ (実際には 3 つ) と、ネストされたローカル スコープ用の特別な規則があります。2 つのスコープは、モジュール レベルの名前用のglobalと、関数内のあらゆるもの用のlocalです。global関数内のステートメント別の方法で宣言しない限り、関数内で代入するものはすべて自動的にローカルになります。関数のどこにも割り当てられていない名前を使用する場合、それはローカル名ではありません。Python は、ネスト関数を (字句的に) 検索して、その名前がローカル名として含まれているかどうかを確認します。ネスト関数がない場合、または名前がそれらのいずれにもローカルでない場合、名前はグローバルであると見なされます。

(グローバル名前空間も特別です。これは、実際にはモジュール グローバル名前空間と組み込みbuiltins名前空間の両方であり、 orモジュールに隠されているため__builtins__です。)

あなたの場合、3 つの x変数があります: モジュール (グローバル) スコープに 1 つ、counter関数に 1 つ、関数に 1 つtemp-+=も代入ステートメントであるためです。名前に割り当てるだけで関数に対してローカルになるため、+=ステートメントはまだ割り当てられていないローカル変数を使用しようとし、UnboundLocalError を発生させます。

xこれら 3 つの参照すべてがグローバル変数を参照することを意図している場合は、と 関数のglobal x両方で行う必要があります。Python 3.x (ただし 2.x ではない) には、の変数に代入するために使用できる宣言とよく似たものがありますが、グローバルはそのままにしておきます。countertempnonlocalglobaltempcounterx

于 2011-05-29T23:33:44.653 に答える
2

Python のクロージャは書き込み可能ではないため、そのようにコードを書くことはできません。関数内の変数から読み取るだけであれば、問題ありません。Python 3 では、nonlocalキーワードを使用して必要な動作を得ることができました。

Python 2.5 以降を使用している場合は、yield キーワードを使用して上記のコードをジェネレーターとして記述することもできます。

于 2011-05-29T23:30:33.997 に答える
2

Python ドキュメントは、質問に詳細に答えています: http://docs.python.org/reference/executionmodel.html#naming-and-binding

要約すると、Python には静的スコープ規則があります。関数 f が変数名を定義または削除する場合、変数名は関数 f のクロージャー内の変数を参照します。関数 f が変数名のみを使用する (定義も削除もしない) 場合、名前は f の親スコープで名前が意味するものを参照します。定義が見つかるまで、またはグローバル スコープに到達するまで、親スコープに進みます。例えば:

def f1(x):
  def g(y):
    z.foo = y # assigns global z
  g(1)

def f2(x):
  def g(y):
    x.foo = y # assigns f2's variable, because f2 defines x 
  g(1)

def f3(x):
  def g(y):
    x = C()
    x.foo = y # assigns g's variable, because g defines x
  g(1)

globalキーワードと (Python 3 では)キーワードはnonlocal、デフォルトのスコープ規則をオーバーライドします。

変数は静的に解決されますが、変数は動的に解決されます。変数の値は、変数がアクセスされた時点でのその変数の最新の定義または削除から取得されます。値は、変数が存在する関数クロージャで検索されます。

于 2011-05-29T23:37:45.830 に答える
1

返信ありがとうございます。誰かが気になる場合に備えて、この問題の回避策を考え出しました。カウンター変数を確立する「スコープ」関数を作成することでそれを行いました。

>>> def gc():
...  def scoper():
...   scoper.s = 0
...   def rtn():
...    scoper.s += 1
...    return scoper.s
...   return rtn
...  return scoper()

上記により、適切なクロージャーを模倣してこれを行うことができました。

>>> a = gc()
>>> a()
1
>>> a()
2
>>> a()
3
>>> b = gc()
>>> b()
1
>>> a()
4
>>> b()
2
于 2011-05-30T08:04:08.977 に答える