sagantaf

メモレベルの技術記事を書くブログ。

pythonの関数とclosureのメモ

python function is first object

>>> def outer():
...     def inner():
...             print('hoge')
...     return inner()
...
>>> outer()
hoge

inner()を返している点に注目。ここではouter()を呼び出すことでprint文の実行結果が返される。 innerを返す内容に変えてみる。

>>> def outer():
...     def inner():
...             print('hoge')
...     return inner
...
>>> outer()
<function outer.<locals>.inner at 0x102931b80>

今度は、outer()を呼び出すとfunctionであることが返される。 ではこれをtestという変数に格納し、()をつけて呼び出すとどうなるか。

>>> test = outer()
>>> test()
hoge

print文の実行結果が返される。つまり下みたいなこともできてしまう。

>>> outer()()
hoge

クロージャ(Function closure)について 関数はローカルスコープとして変数の名前空間を持っている。この名前空間は関数の処理が終わると無くなる。

>>> def hoge(x):
...     print(x)
...
>>> hoge(2)
2
>>> print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

当たり前と言えば当たり前。では以下の場合はどうなるか。

>>> def outer(x):
...     y = 2
...     def inner():
...         print(x, y)
...     return inner
...
>>> hoge = outer(1)

最後の行ではouter(1)の処理が終わった後にreturnsrされるinner関数がhogeに代入されているので、名前空間が消えているのではないか?上記と同じエラーになるのではないか?という疑問が出てくる。 しかし、実行してみると、

>>> hoge
<function outer.<locals>.inner at 0x104afdb80>
>>> hoge()
1 2

ちゃんと意図した通りに実行できる。これは、PythonがFunction closure(クロージャ)という機能を持っているから。クロージャとは、グローバルスコープ以外で定義された関数(この場合inner)が、定義時の自分を囲むスコープの情報を保持する、というもの。

hogeクロージャプロパティ(closure)を確認すると、intオブジェクトを2つ持っていることがわかる。

>>> hoge.__closure__
(<cell at 0x102927c10: int object at 0x10276e108>, <cell at 0x1029a7f40: int object at 0x10276e128>)