web-dev-qa-db-ja.com

Pythonでネストされた関数はどのように機能しますか?

_def maker(n):
    def action(x):
        return x ** n
    return action

f = maker(2)
print(f)
print(f(3))
print(f(4))

g = maker(3)
print(g(3))

print(f(3)) # still remembers 2
_

maker()が返されてaction()が呼び出されるまでに終了しても、ネストされた関数は最初の値_2_を覚えているのはなぜですか?

54
3zzy

親関数で発生したすべての変数が、子関数内の実際の値に置き換えられているのがわかります。このように、子関数を正しく実行するために親関数のスコープを追跡する必要はありません。

「動的に関数を作成する」と考えてください。

def maker(n):
  def action(x):
    return x ** n
  return action

f = maker(2)
--> def action(x):
-->   return x ** 2

これはPythonの基本的な動作であり、複数の割り当てでも同じです。

a = 1
b = 2
a, b = b, a

Pythonはこれを

a, b = 2, 1

基本的に、値を挿入する前に値を挿入します。

30
Tor Valamo

基本的に closure を作成しています。

コンピューターサイエンスでは、クロージャーは語彙環境でバインドされた自由変数を持つファーストクラスの関数です。このような関数は、その自由変数を「閉じた」と言われています。

関連資料: クロージャー:なぜそんなに便利なのですか?

クロージャーは、関数にローカル状態へのアクセスを許可するための、より便利な方法です。

http://docs.python.org/reference/compound_stmts.html から:

プログラマーズノート:関数はファーストクラスのオブジェクトです。関数定義内で実行される「def」フォームは、返されるか渡されるローカル関数を定義します。ネストされた関数で使用される自由変数は、defを含む関数のローカル変数にアクセスできます。詳細については、セクション命名とバインディングを参照してください。

38
miku

2つの関数を定義しています。電話するとき

_f = maker(2)
_

あなたは2倍の数を返す関数を定義しているので、

_f(2) --> 4
f(3) --> 6
_

次に、別の異なる関数を定義します

_g = maker(3)
_

数の3倍を返す

_g(3) ---> 9
_

しかし、それらは2つの異なる関数であり、参照される同じ関数ではなく、それぞれが独立した関数です。関数 'maker'内のスコープ内でさえ同じと呼ばれ、同じ関数ではありません。maker()を呼び出すたびに、異なる関数を定義しています。これはローカル変数のようなもので、関数を呼び出すたびに同じ名前を取りますが、異なる値を含めることができます。この場合、変数 'action'には関数が含まれています(関数は異なる場合があります)

14
Khelben

それが「 closure 」と呼ばれるものです。簡単に言えば、関数を first-class object として扱うほとんどのプログラミング言語ではありませんが、関数オブジェクト内で使用される変数は、関数が生きている限り、囲まれています(つまり記憶されています)。それを利用する方法を知っているなら、それは強力な概念です。

あなたの例では、ネストされたaction関数は変数nを使用するため、その変数の周りにクロージャーを形成し、後の関数呼び出しのために記憶します。

9
Lukman

内部関数を作成する3つの一般的な理由を見てみましょう。

1.クロージャーとファクトリー機能

変数がスコープ外になったり、関数自体が現在のネームスペースから削除されたりしても、外側のスコープの値は記憶されます。

_def print_msg(msg):
    """This is the outer enclosing function"""

    def printer():
        """This is the nested function"""
        print(msg)

    return printer  # this got changed
_

次に、この関数を呼び出してみましょう。

_>>> another = print_msg("Hello")
>>> another()
Hello
_

それは異常です。 print_msg()関数が文字列_"Hello"_で呼び出され、返された関数は名前anotherにバインドされました。 another()の呼び出しでは、print_msg()関数の実行を既に終了しているにもかかわらず、メッセージはまだ記憶されていました。一部のデータ(_"Hello"_)をコードに添付するこの手法は、Pythonではクロージャーと呼ばれます。

それでは、クロージャーは何に適していますか?クロージャーは、グローバル値の使用を回避でき、何らかの形式のデータ非表示を提供します。また、問題に対するオブジェクト指向ソリューションを提供できます。クラスに実装するメソッドがほとんどない場合(ほとんどの場合1つのメソッド)、クロージャは代替のよりエレガントなソリューションを提供できます。 参照

2.カプセル化:

カプセル化の一般的な概念は、外側の世界から内側の世界を隠して保護することです。ここでは、内側の関数は外側の世界の内部でのみアクセスでき、関数の外部で発生するものから保護されます。

3. Keepin 'it DRY

おそらく、多くの場所で同じコードの塊を実行する巨大な関数を持っているでしょう。たとえば、ファイルを処理する関数を作成し、開いているファイルオブジェクトまたはファイル名のいずれかを受け入れたい場合があります。

_def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)
_

詳細については、 this blogを参照してください。

3
sujit tiwari

関数を作成した時点では、n2であったため、関数は次のようになります。

def action(x):
    return x ** 2

F(3)を呼び出すと、x3に設定されるため、関数は3 ** 2を返します。

2
James Polley

1つの用途は、パラメーターを維持する関数を返すことです。

def outer_closure(a):
    #  parm = a               <- saving a here isn't needed
    def inner_closure():
        #return parm
        return a              # <- a is remembered 
    return inner_closure

# set parm to 5 and return address of inner_closure function
x5 = outer_closure(5)
x5()
>5

x6 = outer_closure(6)
x6()
>6

# x5 inner closure function instance of parm persists 
x5()
>5
1
kztd

クロージャについて人々は正しく答えました。つまり、アクション内の「n」の有効な値は、「maker」が呼び出されたときの最後の値です。

これを克服する簡単な方法の1つは、freevar(n)を「アクション」関数内の変数にすることです。この関数は、実行されると「n」のコピーを受け取ります。

これを行う最も簡単な方法は、作成時にデフォルト値が「n」であるパラメーターとして「n」を設定することです。関数のデフォルトパラメータは、関数自体の属性であるTuple(この場合はaction.func_defaults)に格納されるため、「n」のこの値は固定されたままです。

def maker(n):
    def action(x, k=n):
        return x ** k
    return action

使用法:

f = maker(2) # f is action(x, k=2)
f(3)   # returns 3^2 = 9
f(3,3) # returns 3^3 = 27
1
jsbueno

Defキーワードを使用して関数を作成すると、まさにそれを実行します。つまり、新しい関数オブジェクトを作成し、それを変数に割り当てます。指定したコードでは、新しい関数オブジェクトをactionというローカル変数に割り当てています。

2回目に呼び出すときは、2番目の関数オブジェクトを作成します。したがって、fは最初の関数オブジェクト(値の2乗)を指し、gは2番目の関数オブジェクト(値の立方体)を指します。 Pythonが「f(3)」を参照する場合、「変数fを指す関数オブジェクトを実行し、値3を渡す」ことを意味します。fおよびgおよび異なる関数オブジェクトなど異なる値を返します。

0
DavidG