あなたは本当にこのうさぎの穴に落ちたくないのですが、もしあなたが主張すれば、それは可能です. いくつかの作業で。
ネストされた関数は、への呼び出しごとに新たmake_adder()
に作成されます。
>>> import dis
>>> dis.dis(make_adder)
2 0 LOAD_CLOSURE 0 (x)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object adder at 0x10fc988b0, file "<stdin>", line 2>)
9 MAKE_CLOSURE 0
12 STORE_FAST 1 (adder)
4 15 LOAD_FAST 1 (adder)
18 RETURN_VALUE
そこのオペコードは、親関数からMAKE_CLOSURE
参照するネストされた関数であるクロージャを持つ関数を作成します (オペコードは関数のクロージャ セルを構築します)。x
LOAD_CLOSURE
関数を呼び出さないとmake_adder
、コード オブジェクトにしかアクセスできません。make_adder()
関数コードとともに定数として格納されます。のバイト コードは、スコープ付きセルとして変数adder
にアクセスできることをx
前提としていますが、これにより、コード オブジェクトはほとんど役に立たなくなります。
>>> make_adder.__code__.co_consts
(None, <code object adder at 0x10fc988b0, file "<stdin>", line 2>)
>>> dis.dis(make_adder.__code__.co_consts[1])
3 0 LOAD_DEREF 0 (x)
3 LOAD_FAST 0 (y)
6 BINARY_ADD
7 RETURN_VALUE
LOAD_DEREF
クロージャ セルから値をロードします。コード オブジェクトを再び関数オブジェクトにするには、それを関数コンストラクターに渡す必要があります。
>>> from types import FunctionType
>>> FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (5,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: arg 5 (closure) expected cell, found int
しかし、ご覧のとおり、コンストラクターは整数値ではなくクロージャーを見つけることを期待しています。クロージャーを作成するには、自由変数を持つ関数が必要です。コンパイラによってクローズオーバーに使用できるとマークされたもの。そして、これらの閉じられたオーバー値を返す必要があります。それ以外の方法でクロージャを作成することはできません。したがって、クロージャーを作成するためだけにネストされた関数を作成します。
def make_closure_cell(val):
def nested():
return val
return nested.__closure__[0]
cell = make_closure_cell(5)
adder()
これで、 を呼び出さずに再作成できますmake_adder
:
>>> adder = FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (cell,))
>>> adder(10)
15
たぶん、電話するだけのmake_adder()
方が簡単だったでしょう。
ちなみに、ご覧のとおり、関数は Python のファースト クラス オブジェクトです。make_adder
はオブジェクトであり、追加(somearguments)
することで呼び出し、または関数を呼び出します。この場合、その関数は別の関数オブジェクトを返しますが、これも呼び出すことができます。adder()
上記のを呼び出さずに作成する方法の曲がりくねった例では、関数オブジェクトを呼び出さずmake_adder()
に参照しました。make_adder
たとえば、それに接続されている Python バイト コードを逆アセンブルしたり、そこから定数やクロージャーを取得したりします。同様に、関数は関数オブジェクトをmake_adder()
返します。ポイントはadder
、後でそれを呼び出す他の何かのためにその関数を作成することです。make_adder()
上記のセッションは、Python 2 と 3 の間の互換性を念頭に置いて実施されました。古いバージョンの Python 2 も同じように動作しますが、詳細の一部が少し異なります。func_code
一部の属性には、たとえば、代わりになどの異なる名前が付いています__code__
。核心的な詳細を知りたい場合は、inspect
モジュールとPython データモデルでこれらに関するドキュメントを参照してください。