Qiita記事「Pythonのクロージャについて: 関数のスコープと、関数が第一級オブジェクトであることからちゃんと考える」の蛇足
Qiitaに Pythonのクロージャについて、自分なりにまとめた記事を投稿しました (※ブログに記事を移しました)
(https://www.kangetsu121.work/entry/2019/04/28/232143)。
ただでさえ記事が結構長くなったのと、この記事に書くとちょっと蛇足かな、
と思った点について、折角調べて気づいたことなのでこっちに書いときます。
Qiita記事を読んでなくてここにたどり着いた方は、ぜひそちらもご一読いただけると嬉しいです。
Qiitaとブログの住み分けに悩んでたので、ライトな補足や蛇足を載せてしまえ、と思ったのでした。
クロージャとなったオブジェクトには、__closure__ アトリビュートに環境情報が値として格納されている
そもそも Pythonのオブジェクトは、デフォルトで __closure__
というアトリビュートを持ってるみたいですね。
例えばこんなコードで確認してみます。
def createCounter(): cnt = [0] print("running createCounter") print("createCounter locals are: " + str(locals())) def inner(): cnt[0] += 1 # cnt[0] = cnt[0] + 1 print(cnt) print("running inner") print("inner locals are: " + str(locals())) return inner counter = createCounter()
こうすると、変数 counter
はクロージャですね。
この counter
のアトリビュートを一覧してみます。
>>> dir(counter) # counterのアトリビュート一覧 (可読性のため改行) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
確かに、__closure__
というものがあります。
次はこいつの中身を見てみます。
>>> counter.__closure__ (<cell at 0x0000021B881AC828: list object at 0x0000021B88251308>,)
list object at 0x0000021B88251308
と出力されました。
リストオブジェクトを持っていることが記録されてるんですね。
もちろん、ここでいうリストオブジェクトは cnt
を指しています。
さらにその値を確かめてみましょう。
__closure__
のインデックス 0の cell_contents
に、内容が格納されているらしいです。
>>> counter.__closure__[0].cell_contents [0] >>> counter() [1] running inner >>> counter.__closure__[0].cell_contents [1] # cntが更新された = counterの環境内の cntが更新された >>>
おお、確かに cnt
の値が出力されて、 cnt
を更新すると同じように更新されてますね。
ちなみに、クロージャでないオブジェクトも __closure__
アトリビュートはありましたが、中身は空でした。
以上、気づいたけど記事に載せるには盛り過ぎかな、と思ったのでブログに書きました。
こうやって調べていくと楽しいですが、どんどん深みにはまってる感がありますね。
楽しいですが、時々は一次退却もしながらプログラミングを進めたいと思います。