python3x: what would Python print?, local environment versus global environment etc


講義を聞いていたらpop quiz (for nothing unfortunately...)が出てきた。globalとlocalを理解する上で役に立つと思いまとめてみることにする。問題はこちら:

What would Python print?#1

def f():
    return 0

def g():
    print(f())

def h():
    def f():
        return 1
    g()

h()

My Initial Understanding to it

  1. globalフレーム内でfunc f()func g()func h()が定義される。
  2. func h()を呼んだのでfunc h()の中身に入る。func h()内のローカル領域でfunc f()が定義され、func g()を呼ぶ。
  3. pythontutor.comを参考にすると分かりやすいが通常指定された変数を探す場合は今自分のいるlocal frameから上の層(一番上の層がglobal frame)に行く。従ってg()を呼んでfunc h()の中のローカルであるdef f(): return 1をボトムアップで一番新しく定義されたdef f(): return 1にたどり着き1を返す。

ただこれには大きい誤解があるのではないかと思い始めてきた。

Defining Environments

その前に関数を呼ぶ(新しいenvironmentもしくはフレームを作る)ということはどういうことなのか講義スライドを参考に考えてみる。

一番最後の:

when a user-defined function value is called, the local frame that is created for that call is linked to the defining frame of the function

が最大のヒントだと思う。どこで呼ばれたかというよりはどこで定義されたかのほうが重要なのではないかと。つまり

  1. globalフレーム内でfunc f()func g()func h()が定義される。
  2. func h()を呼んだのでfunc h()の中身に入る。func h()内のローカル領域でfunc f()が定義され、func g()を呼ぶ。
  3. 呼ばれた関数が作られた領域へと移動する(in this case, func g()'s parent frame is global, in which it was defined)。func g()の中に入って行くとprint(f())があるので(グローバル領域内で)func f()を探す。
  4. グローバル領域にあるfunc f()def f(): return 0なので0と表示される。

逆に言えばfunc g()func h()のローカルフレームで定義されたdef f(): return 1にアクセスすることが出来ないのではないかと考えている。理由は単純にfunc g()を呼んだ時点でグローバル領域にいるので別にfunc h()を呼んで中に入るか、もしくはfunc g()func h()の中にfunc g()を入れるかしないとアクセス出来ない。

I wonder as I wander

ひとつ目の疑問は上の仮説が正しいのならばもし仮にfunc g()func h()内にいれたら違う結果になるのか?

def f():
    return 0

def h():
    def f():
        return 1
    def g():
        print(f())
    g()

h() # 1

恐らくこれは正しいみたい。

2つ目はこれは関数だけに適用することなのか。スライド上ではあえて(それとも深い意味は無いのか?)function valueと言っていたのでそうなのかもしれない??

考えてみたら変数に関しても同じ考え方で良いと思う。一番簡単な考え方は数ある同じ名前を持った変数xが合ったとして最終的にreturnもしくはprintの一番近くにあるものが(もっと適切な言い方をすれば最後にxとして再定義されたもの)がprintされると考えていいのかもしれない。

What would Python print?#2

続いての問題はこちら:

def f():
    return 0

g = f

def f():
    return 1

print(g())

個人的にはこっちのほうが簡単だった。単純にfunc f()が定義されてgfunc f()とリンクされてその後にfunc f()がもう一つの(下の)func f()として再アサインされる。よってgは上のfunc f()となる。

まさにスライドに書いてあるとおりこれはx=3: y=x: x=4: print(y)と同じ。ちなみにこれは上で書いた疑問の回答にもなる。

正確な動きを見たい場合はpythontutor.comを。

What would Python print?#3

def f():
    return 0

def g():
    print(f())

def f():
    return 1

g()

これは上の問題を関数に変えたのと同じ。なので答えは1となる。

What would Python print?#4

def g(x):
    print(x)

def f(f):
    f(1)

f(g)

実際にpythontutor.comで見れば動きはわかるが、簡単流れは:

  1. g(x)f(f)が定義される。
  2. f(g)で呼ぶ。この時parameterffunc g(x)として呼んでいるので実際はf(g()) w/ no argument yetとなる。
  3. 次にf(1)を呼んでいるがローカル領域上でfg()にアサインされているので実際はg(1)を呼んでいるのと同じ。
  4. g(1)を呼びprint(1)つまりvalueNoneを返してinterpreter上に1と表示される。

What would Python print?#5

def f():
    return 0

def g():
    return f()

def h(k):
    def f():
        return 1
    p = k
    return p()

print(h(g))

これは上の問題を統合して作ったというイメージ。上の問題ができればこれもすんなり出来るはず。ポイントは関数が呼ばれたら必ずしもローカルフレームから探すわけではないということその関数が定義されたところから探す。ここではg()はグローバルで定義されているのでh(k)は見向きもせず上のf()を見付けて0を返す。

Assignments don't themselves create new values, but only copy old ones, so that when p is evaluated, it is equal to k, which is equal to g, which is attached to the global environment

What would Python print?#6

def f(p,k):
    def g():
        print(k)
    if k == 0:
        f(g,1)
    else:
        p()
f(None, 0)

これもp()(=g())を呼んだ時にg()が定義されたところ(=global)に戻ってからkを探す。つまり答えは0