前回の記事(スコープの見え方について調べてみた)の記事の内容を踏まえた上で、クロージャの実装について見てみる。
def counter(x):
pprint(locals())
def count():
pprint(locals())
y = x
x += 1
return y
return count
c = counter(10)
print c()
print c()
print c()
は、動きそうに見えるが、動かすと、次のようにエラーになる。
実行結果
{'x': 10}
{}
Traceback (most recent call last):
File "tes004.py", line 12, in <module>
print c()
File "tes004.py", line 7, in count
y = x
UnboundLocalError: local variable 'x' referenced before assignment
関数counter()の引数xは、counter()のローカル変数として見える。
内部関数count()から変数xを参照だけするだけであれば、変数xはcount()のローカル変数として参照できるが、代入コード(x += 1
⇒これは参照のコードでもある)があるため、count()関数ブロックで定義される変数として動こうするが、代入の前に参照が実行行われるためエラーとなる。
外側の関数で定義された変数の参照はできるが、代入(書き換え)はできない。
次は、上手く実行できるパターン。
def counter(x):
pprint(locals())
y = [x]
def count():
pprint(locals())
z = y[0]
y[0] += 1
return z
return count
c = counter(10)
print c()
print c()
print c()
実行結果
{'x': 10}
{'y': [10]}
10
{'y': [11]}
11
{'y': [12]}
12
このコードも、内部関数count()で変数yを書き換えているように見えるが、変数yで参照できるリストの中身を書き換えているだけなので、正常に動く。
例えば、y[0] += 1
をy = [y[0] + 1]
のようにすると、エラーになる。これは変数yを書き換えようとしているため。
このため、参照先がリストでなく、クラスとかであっても、参照先を更新することはできる。
次は、クラスで実装した例。
class tcls():
def __init__(self, x):
self.x = x
def counter(x):
pprint(locals())
y = tcls(x)
def count():
pprint(locals())
z = y.x
y.x += 1
return z
return count
c = counter(10)
print c()
print c()
print c()
実行結果
{'x': 10}
{'y': <__main__.tcls instance at 0x0222F788>}
10
{'y': <__main__.tcls instance at 0x0222F788>}
11
{'y': <__main__.tcls instance at 0x0222F788>}
12