python3: nonlocal vs global


復習を込めて授業をサラッと見ていたらnonlocalglobalに。自問自答してパット答えられなかったのでqiitaに書くことに。

global and nonlocal

そもそもなんでこんなのが必要なのかというと、(pythonでは)プログラムは一度処理を終えてしまうとメモリが自動的に消えてしまうので次回同じプログラムを開始させても初期状態からスタートしてしまう。状況によってはそれが不都合だとされることがある。そこでnonlocal (or global)。これを使えば処理を終えても指定した変数の記録を残して次の処理を実行できるというわけだ。詳しくは一番下の例を。

_global says that whenever it sees x0(that's when the x0 is mentioned inside of f2), we will go for the definition of x0 in the global frame.

nonlocal says whenever you see x1 mentioned, we will look for x1 in some frame outside of f2

x = 0
def outer():
    x = 1
    def inner():
        x = 2
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
print("global:", x)

# inner: 2
# outer: 1
# global: 0

これにnonlocalを加えると

x = 0
def outer():
    x = 1
    def inner():
        nonlocal x
        x = 2
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
print("global:", x)

# inner: 2
# outer: 2
# global: 0

xの中身をそのままキープさせたい時などに役に立ちそう。

def stepper(num):
    def step():
        nonlocal num
        num += 1
        return num
    return step
>>> stepper(4)()
5

たまたま自分で作ったメモを見つけたのでこれを元に。。

def counter():
    count = 0
    def counter():
        count += 1
        return count
    return counter
# triggers unboundlocalerror since its not defined inside the counter func.

def counter2():
    count = 0
    def counter():
        count = 0
        count += 1
        return count
    return counter
"""
c = counter2() always returns 1
since each time the counter func gets called,
it creates another local frame with new info unless you have nonlocal within it
"""

def counter3():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return count

対してglobal

x = 0
def outer():
    x = 1
    def inner():
        global x
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# inner: 2
# outer: 1
# global: 2

結果的にglobalでもnonlocalでもnested functionの中にある変数(1つのみ)をglobalとして扱うかnonlocal(ある意味local)として扱うかだけの問題。globalを使えば指定した変数はglobal変数を無視してglobal xが定義されたところにあるxを参考にすると。

ちょっとした問題

>>> x0, y0 = 0, 0
>>> def g1(x1, y1):
...     def f2():
...         nonlocal x1
...         global x0
...         x0, x1 = 1,2
...         y0, y1 = 1,2
...     f2()
...     print(x0, x1, y0, y1)
... 
>>> g1(0,0)
1, 2, 0, 0
>>print(x0, y0)
1, 0

x0x1がグローバルかノンローカルの違いを表している。

この例はもっとわかりやすいかもしれない。globalはnested funcitonの中に配置しないと意味が無い。

def f():
    global x
    def g(): x = 3 # Local x
    g()
    return x
>>> x = 0
>>> f()
0
def f():
    def g():
        global x
        x = 3
    g()
    return x
>>>x = 0
>>> f()
3 

直感的にわかりやすそうな例

def make_switcher(f,g,x,y):
"""
a function, which takes in two funcs, f and g, and two #s, x and y. It returns a new function that performs f x-times, and then switches to g for y-times, and then back to f, etc. Assume that f and g each take one arg.
>>>crazy_ivan = make_switcher(lambda x: x*x, lambda x: x+1, 2, 1)
>>>crazy_ivan(4)
16
>>>crazy_ivan(5)
25
>>>crazy_ivan(4)
5
>>>crazy_ivan(4)
16
"""
    counter = 0
    def swithcer(n):
        nonlocal counter, f, g, x, y
    if counter == x:
        counter = 0
        f,g = g,f
            x, y = y, x
    counter += 1
    return f(n)
    return swithcer

その他、参考になりそうなもの

pythontutor.comも役に立つが個人的には自分で書いていったほうが楽しいのでレクチャーにあったショートハンドを参考にしながら勉強するのも面白そうだ。分かりやすく、覚えやすい。