7

誰かが次のプログラムが失敗する理由を説明できますか:

def g(f):
  for _ in range(10):
    f()

def main():
  x = 10
  def f():
    print x
    x = x + 1
  g(f)

if __name__ == '__main__':
  main()

メッセージ付き:

Traceback (most recent call last):
  File "a.py", line 13, in <module>
    main()
  File "a.py", line 10, in main
    g(f)
  File "a.py", line 3, in g
    f()
  File "a.py", line 8, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

しかし、単純に変数xを配列に変更すると、次のように機能します。

def g(f):
  for _ in range(10):
    f()

def main():
  x = [10]
  def f():
    print x[0]
    x[0] = x[0] + 1
  g(f)

if __name__ == '__main__':
  main()

出力で

10
11
12
13
14
15
16
17
18
19

私が混乱している理由は、f()アクセスできないx場合x、配列の場合にアクセス可能になるのはなぜですか?

ありがとう。

4

4 に答える 4

5

しかし、この答えは、問題は x への代入にあると言っています。それだけなら、印刷しても問題ないはずですよね?

物事が起こる順序を理解する必要があります。Python コードをコンパイルして実行する前に、パーサーと呼ばれるものが Python コードを読み取り、構文をチェックします。パーサーが行うもう 1 つのことは、変数をローカルとしてマークすることです。パーサーがコード内の割り当てをローカル スコープで検出すると、割り当ての左側の変数がローカルとしてマークされます。その時点では、まだ何もコンパイルされておらず、ましてや実行されていないため、代入は行われません。変数は単にローカル変数としてマークされています。

パーサーが終了すると、コードがコンパイルされて実行されます。実行が print ステートメントに到達すると、次のようになります。

def main():
  x = 10     #<---x in enclosing scope

  def f():
    print x    #<-----

    x = x + 1  #<-- x marked as local variable inside the function f()

print ステートメントは先に進み、囲んでいるスコープ (LEGB ルックアップ プロセスの 'E') に x を出力する必要があるように見えます。ただし、パーサーは以前に x を f() 内のローカル変数としてマークしていたため、Python はローカル スコープ (LEGB ルックアップ プロセスの 'L') を超えて x をルックアップしません。'print x' の実行時にローカル スコープで x が割り当てられていないため、python はエラーを吐き出します。

割り当てが行われるコードが決して実行されない場合でも、パーサーは割り当ての左側の変数をローカル変数としてマークすることに注意してください。パーサーは、物事がどのように実行されるかを認識していないため、実行できないコードであっても、ファイル全体で構文エラーやローカル変数をやみくもに検索します。その例を次に示します。

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            a b c  #...yet the parser finds a syntax error here


    return f

f = dostuff()
f()



--output:--
File "1.py", line 8
     a b c
      ^
SyntaxError: invalid syntax

パーサーは、ローカル変数をマークするときに同じことを行います。

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            x = 0  #..yet the parser marks x as a local variable

    return f

f = dostuff()
f()

最後のプログラムを実行するとどうなるか見てみましょう:

Traceback (most recent call last):
  File "1.py", line 11, in <module>
    f()
  File "1.py", line 4, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

ステートメント「print x」が実行されると、パーサーが x をローカル変数としてマークしたため、x のルックアップはローカル スコープで停止します。

その「機能」は python に固有のものではなく、他の言語でも発生します。

配列の例については、次のように記述します。

x[0] = x[0] + 1

これは、x という名前の配列を検索し、最初の要素に何かを割り当てるように Python に指示します。ローカル スコープには x という名前の割り当てがないため、パーサーは x をローカル変数としてマークしません。

于 2013-05-09T02:23:02.167 に答える
4

その理由は、最初の例で代入演算を使用したためです。そのためx = x + 1、関数が定義されたとき、Python はそれxがローカル変数であると考えました。しかし、実際に関数 python を呼び出したとき、xローカルで RHS の値を見つけることができなかったため、エラーが発生しました。

割り当ての代わりに2番目の例では、変更可能なオブジェクトを単純に変更したため、pythonは異議を唱えることはなくx[0]、囲んでいるスコープから値を取得します(実際には、最初に囲んでいるスコープで検索し、次にグローバルスコープで検索し、最後にビルトインですが、見つかるとすぐに停止します)。

Python 3x では、nonlocal キーワードを使用してこれを処理できます。py2x では、内部関数に値を渡すか、関数属性を使用できます。

関数属性の使用:

def main():
  main.x = 1
  def f():
      main.x = main.x + 1
      print main.x
  return f

main()()   #prints 2

値を明示的に渡す:

def main():
  x = 1
  def f(x):
      x = x + 1
      print x
      return x
  x = f(x)     #pass x and store the returned value back to x

main()   #prints 2

nonlocalpy3x での使用:

def main():
  x = 1
  def f():
      nonlocal x
      x = x + 1
      print (x)
  return f

main()()  #prints 2
于 2013-05-09T01:41:43.733 に答える
1

問題は、変数xがクロージャによって取得されることです。クロージャーから取得された変数に代入しようとすると、 or 1キーワードを使用しない限り、Python は文句を言います。を使用している場合、名前に割り当てていません-クロージャーで取得されたオブジェクトを変更できますが、それに割り当てることはできません。globalnonlocallist


基本的に、エラーはprint x行で発生します。これは、python が関数を解析するときに、それxが割り当てられていることがわかりx、ローカル変数である必要があると見なされるためです。行に到達するとprint x、python はローカルを検索しようとしますxが、そこにはありません。dis.disこれは、 を使用してバイトコードを検査することで確認できます。ここで、python は非ローカル変数に使用される命令ではLOAD_FASTなく、ローカル変数に使用される命令を使用します。LOAD_GLOBAL

通常、これは を引き起こしますが、python はinまたは2NameErrorを探すことでもう少し役に立ちます。それらのいずれかで見つかった場合、何が起こっているのかをよりよく理解するために代わりに発生します-見つからなかった(「バインド」されていない)ローカル変数があります。xfunc_closurefunc_globals xUnboundLocalError

1 python3.x のみ

2 python2.x -- python3.x では、これらの属性がそれぞれ__closure__およびに変更されました。__globals__

于 2013-05-09T01:35:12.660 に答える