13

別の関数内の関数にどのようにアクセスできるのか疑問に思っています。次のようなコードを見ました。

>>> def make_adder(x):
      def adder(y):
        return x+y
      return adder
>>> a = make_adder(5)
>>> a(10)
15

adder では、関数を呼び出す別の方法はありますか? 2 番目の質問は、最後の行でなぜaddernotと呼ぶのadder(...)かということです。

良い説明は大歓迎です。

4

5 に答える 5

25

あなたは本当にこのうさぎの穴に落ちたくないのですが、もしあなたが主張すれば、それは可能です. いくつかの作業で。

ネストされた関数は、への呼び出しごとに新た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参照するネストされた関数であるクロージャを持つ関数を作成します (オペコードは関数のクロージャ セルを構築します)。xLOAD_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 データモデルでこれらに関するドキュメントを参照してください。

于 2013-06-30T22:14:21.123 に答える
7

いいえ、 へのローカル変数であるため、直接呼び出すことはできませんmake_adder

を呼び出したときに関数オブジェクトが返されたadder()ため、使用する必要があります。この関数オブジェクトを実行するには、必要ですreturn adderaddermake_adder(5)()

def make_adder(x):
       def adder(y):
           return x+y
       return adder
... 
>>> make_adder(5)             #returns the function object adder
<function adder at 0x9fefa74>

ここでは、関数によって返されたようにアクセスできるため、直接呼び出すことができますmake_adder。返されたオブジェクトは実際にはクロージャーと呼ばれます。これは、関数make_addrが既に返されていadderても、それによって返された関数オブジェクトが引き続き変数にアクセスできるためですx。py3.x では、xusingnonlocalステートメントの値を変更することもできます。

>>> make_adder(5)(10)          
15

Py3.x の例:

>>> def make_addr(x):
        def adder(y):
                nonlocal x
                x += 1
                return x+y
        return adder
... 
>>> f = make_addr(5)
>>> f(5)               #with each call x gets incremented
11
>>> f(5)
12

#g gets it's own closure, it is not related to f anyhow. i.e each call to 
# make_addr returns a new closure.
>>> g = make_addr(5)  
>>> g(5)
11 
>>> g(6)
13
于 2013-06-30T22:15:16.370 に答える
4

adder関数を呼び出した結果ではなく、関数を呼び出し元に返すため、括弧がありません。

make_adderが戻ってくるのでadder、すでに に直接アクセスできますadder。実際、a(10)は への呼び出しadder(10)です。

于 2013-06-30T22:15:05.903 に答える