私は閉鎖について多くのことを読んでいて、それらを理解していると思いますが、自分自身や他の人の状況を曇らせることなく、誰かが閉鎖をできるだけ簡潔かつ明確に説明できることを願っています. どこで、なぜそれらを使用したいかを理解するのに役立つ簡単な説明を探しています。
13 に答える
オブジェクトはメソッドが添付されたデータであり、クロージャはデータが添付された関数です。
def make_counter():
i = 0
def counter(): # counter() is a closure
nonlocal i
i += 1
return i
return counter
c1 = make_counter()
c2 = make_counter()
print (c1(), c1(), c2(), c2())
# -> 1 2 1 2
それは単純です: 潜在的に制御フローがそのスコープを離れた後に、包含スコープから変数を参照する関数。その最後のビットは非常に便利です:
>>> def makeConstantAdder(x):
... constant = x
... def adder(y):
... return y + constant
... return adder
...
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7
12 と 4 はそれぞれ f と g の内部で「消えた」ことに注意してください。この機能により、f と g は適切なクロージャになります。
正直なところ、私はクロージャーを完全によく理解していますが、「クロージャー」とは何か、それについて「クロージャー」とは何かを正確に理解したことがありません。用語の選択の背後にあるロジックを探すのをあきらめることをお勧めします。
とにかく、ここに私の説明があります:
def foo():
x = 3
def bar():
print x
x = 5
return bar
bar = foo()
bar() # print 5
ここでの重要なアイデアは、「x」がスコープ外に出て機能していない場合でも、foo から返される関数オブジェクトがローカル変数「x」へのフックを保持するということです。このフックは、var がその時点で持っていた値だけでなく、var 自体に対するものであるため、bar が呼び出されると、3 ではなく 5 が出力されます。
また、Python 2.x のクロージャーには制限があることも明確にしてください。「x = bla」と記述すると、foo の「x」に代入するのではなく、bar でローカルの「x」を宣言するため、「bar」内の「x」を変更する方法はありません。 . これは、Python の代入 = 宣言の副作用です。これを回避するために、Python 3.0 では nonlocal キーワードが導入されています。
def foo():
x = 3
def bar():
print x
def ack():
nonlocal x
x = 7
x = 5
return (bar, ack)
bar, ack = foo()
ack() # modify x of the call to foo
bar() # print 7
私はこのラフで簡潔な定義が好きです:
アクティブでなくなった環境を参照できる関数。
追加します
クロージャを使用すると、変数をパラメータとして渡すことなく、変数を関数にバインドできます。
パラメータを受け入れるデコレータは、クロージャの一般的な使用法です。クロージャは、その種の「関数ファクトリ」の一般的な実装メカニズムです。戦略が実行時にデータによって変更される場合、戦略パターンでクロージャを使用することを頻繁に選択します。
匿名のブロック定義を許可する言語(Ruby、C#など)では、クロージャを使用して、新しい新しい制御構造を実装できます。匿名ブロックの欠如は、Pythonのクロージャの制限の1つです。
クロージャとは何かを説明するのと同じコンテキストでトランザクションが使用されていることは聞いたことがありません。実際、ここにはトランザクションのセマンティクスはありません。
これは、外部変数(定数)を「閉じる」ため、クロージャーと呼ばれます。つまり、関数だけでなく、関数が作成された環境のエンクロージャーでもあります。
次の例では、xを変更した後にクロージャgを呼び出すと、gがxを閉じるため、g内のxの値も変更されます。
x = 0
def f():
def g():
return x * 2
return g
closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4
これがクロージャの典型的なユースケースです-GUI要素のコールバック(これはボタンクラスをサブクラス化する代わりになります)。たとえば、ボタンの押下に応答して呼び出される関数を作成し、クリックの処理に必要な親スコープ内の関連する変数を「閉じる」ことができます。このようにして、同じ初期化関数から非常に複雑なインターフェースを接続し、すべての依存関係をクロージャーに組み込むことができます。
Python では、クロージャーは変数が不変にバインドされている関数のインスタンスです。
実際、データモデルは関数の__closure__
属性の説明でこれを説明しています:
なし、または関数の自由変数のバインディングを含むセルのタプル。読み取り専用
これを実証するには:
def enclosure(foo):
def closure(bar):
print(foo, bar)
return closure
closure_instance = enclosure('foo')
明らかに、変数 name からポイントされる関数があることがわかりますclosure_instance
。表向きは、オブジェクト でそれを呼び出すと、文字列と、 の文字列表現が何であれbar
、それを出力する必要があります。'foo'
bar
実際、文字列 'foo'はcell_contents
関数のインスタンスにバインドされており、属性のタプルの最初の (そして唯一の) セルの属性にアクセスすることで、ここで直接読み取ることができ__closure__
ます。
>>> closure_instance.__closure__[0].cell_contents
'foo'
余談ですが、セル オブジェクトは C API ドキュメントで説明されています。
そして、クロージャの使用法を示すことができます。これ'foo'
は、関数内でスタックしており、変更されていないことに注意してください。
>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux
そして、何もそれを変えることはできません:
>>> closure_instance.__closure__ = None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: readonly attribute
部分関数
与えられた例ではクロージャーを部分関数として使用していますが、これが唯一の目標である場合、同じ目標は以下で達成できますfunctools.partial
>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux
部分関数の例には当てはまらない、より複雑なクロージャーもあります。時間が許す限り、それらをさらに詳しく説明します。
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.
# Defining a closure
# This is an outer function.
def outer_function(message):
# This is an inner nested function.
def inner_function():
print(message)
return inner_function
# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes
# even if they are not present in memory is called closures
# Output: Hello
Closures が満たす基準は次のとおりです。
- ネストされた関数が必要です。
- ネストされた関数は、外側の関数で定義された値を参照する必要があります。
- 囲んでいる関数は、ネストされた関数を返す必要があります。
# Example 2
def make_multiplier_of(n): # Outer function
def multiplier(x): # Inner nested function
return x * n
return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) # 6
私にとって、「クロージャー」は、作成された環境を記憶できる機能です。この機能により、クロージャー内で変数またはメソッドを使用できます。別の方法では、それらがもう存在しないか、スコープのために手の届かないために使用できません。Ruby でこのコードを見てみましょう。
def makefunction (x)
def multiply (a,b)
puts a*b
end
return lambda {|n| multiply(n,x)} # => returning a closure
end
func = makefunction(2) # => we capture the closure
func.call(6) # => Result equal "12"
「乗算」メソッドと「x」変数の両方が存在しなくなった場合でも機能します。すべては、記憶する閉鎖機能のためです。
私が今まで見た閉鎖についての最良の説明は、メカニズムを説明することでした。次のようになりました。
プログラム スタックが、各ノードに子が 1 つだけあり、単一のリーフ ノードが現在実行中のプロシージャのコンテキストである縮退ツリーであると想像してください。
ここで、各ノードが 1 つの子のみを持つことができるという制約を緩和します。
これを行うと、ローカル コンテキストを破棄せずにプロシージャから戻ることができる構造 ('yield') を持つことができます (つまり、戻ったときにスタックからポップしません)。次にプロシージャが呼び出されると、呼び出しは古いスタック (ツリー) フレームを取得し、中断したところから実行を続けます。