静かなる名辞

pythonとプログラミングのこと


【python】関数内関数は動的に生成される

 わかっている人には当たり前のことですが、他の言語から来た人だと「んんん?」かもしれません。

こうなる

>>> def f():
...     def g():
...         pass
...     return g
... 
>>> a = f()
>>> b = f()
>>> a is b
False
>>> id(a)
139834257176640
>>> id(b)
139834231735568

解説

 わかりやすいように、disモジュール(標準)のバイトコードディスアセンブラで見てみましょうか。

>>> import dis
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (<code object g at 0x7f2db33a4a50, file "<stdin>", line 2>)
              3 LOAD_CONST               2 ('f.<locals>.g')
              6 MAKE_FUNCTION            0
              9 STORE_FAST               0 (g)

  4          12 LOAD_FAST                0 (g)
             15 RETURN_VALUE

 中身については深く検討していないのですが*1、MAKE_FUNCTIONがあることはわかりますね。

 これが関数呼び出しのたびに実行され、新たな関数オブジェクトが生成されている訳です。

 名前空間だけ間借りしている訳ではありません。

なにか問題なのか

 これのせいで微妙に遅くなる可能性はあるので、測ります。

 コード内で使っているFizz Buzzのコードはこちらからお借りしました。

PythonでFizz Buzz書いてみた - Qiita

import timeit

def fizzbuzz(n):
    for i in range(1, n):
        if i % 15 == 0:
            print("Fizz Buzz!")
        elif i % 3 == 0:
            print("Fizz!")
        elif i % 5 == 0:
            print("Buzz!")
        else:
            print(i)

def f():
    def fizzbuzz_infunc(n):
        for i in range(1, n):
            if i % 15 == 0:
                print("Fizz Buzz!")
            elif i % 3 == 0:
                print("Fizz!")
            elif i % 5 == 0:
                print("Buzz!")
            else:
                print(i)
    return fizzbuzz_infunc

def g():
    return fizzbuzz

print(timeit.timeit(f, number=10**6))
print(timeit.timeit(g, number=10**6))
""" 結果=>
0.13979517295956612
0.08669622003799304
"""

 まあ微妙に遅いけど、気にするほどではないかも。

 これを利用するとクロージャが作れるというのは有名な話ですね。

*1:そもそも私はpythonのバイトコードなんか読めない