Python のクロージャはすぐに理解できる概念ではありませんが、学習を深めていくと、どうしてもそのようなことを理解する必要があります。
クロージャの概念
クロージャを概念的に理解してみましょう。
一部の言語では、関数内に別の関数を (ネストして) 定義できる場合、内部関数が外部関数の変数を参照すると、クロージャが発生することがあります。クロージャを使用すると、関数と一連の「プライベート」変数の間の関連付けを作成できます。これらのプライベート変数は、特定の関数への複数の呼び出しにわたって永続性を維持します。
——Wikipedia)
わかりやすく言うと、関数がオブジェクトとして返されると、外部変数が取り込まれてクロージャーを形成します。例を参照してください。
def make_printer(msg): def printer(): print msg # 夹带私货(外部变量) return printer # 返回的是函数,带私货的函数 printer = make_printer('Foo!') printer()
関数をオブジェクトとして使用することをサポートするプログラミング言語は、通常、クロージャをサポートします。 Python、JavaScriptなど。
クロージャを理解する方法
クロージャの意味は何ですか?なぜクロージャが必要ですか?
クロージャの意味は、外部変数(プライベートグッズ)を運ぶことだと個人的には考えています。通常の関数と何ら変わりません。同じ機能が、異なる機能を実現するために異なる私有物を運ぶ。実際、クロージャの概念は、インターフェイス指向プログラミングの概念と非常に似ており、軽量のインターフェイスのカプセル化として理解することもできます。
インターフェイスは、メソッド シグネチャの一連の制約ルールを定義します。
def tag(tag_name): def add_tag(content): return "<{0}>{1}</{0}>".format(tag_name, content) return add_tag content = 'Hello' add_tag = tag('a') print add_tag(content) # <a>Hello</a> add_tag = tag('b') print add_tag(content) # <b>Hello</b>
この例では、コンテンツにタグを追加する関数が必要ですが、具体的な tag_name は実際のニーズに基づいて決定されます。外部呼び出しのインターフェイスは add_tag(content) です。インターフェース指向で実装する場合は、まずインターフェースとして add_tag を記述し、そのパラメーターと戻り値の型を指定してから、a と b の add_tag をそれぞれ実装します。
しかし、クロージャの概念では、add_tag は 2 つのパラメータ、tag_name と content を必要とする関数ですが、パラメータ tag_name はパックされています。そこで、最初に梱包して持ち帰る方法を教えてください。
上記の例はあまり鮮明ではありませんが、実際、クロージャの概念は私たちの生活や仕事の中でも非常に一般的です。たとえば、携帯電話でダイヤルするときは、通話の宛先だけを気にし、携帯電話の各ブランドがどのように実装しているか、どのモジュールが使用されているかについては気にしません。別の例としては、レストランに食事をするときに、料金を支払う必要があるのはサービスを楽しむためだけであり、その食事にどれだけのガター油が使用されたかわかりません。これらは、いくつかの機能またはサービス (電話をかける、食事など) を返すクロージャとみなすことができますが、これらの関数は外部変数 (アンテナ、オイルの排出など) を使用します。
このクラスを構築するときは、さまざまなパラメーターを使用します。これらのパラメーターは、このクラスによって提供される外部メソッドです。ただし、クラスはクロージャよりもはるかに大きくなります。クロージャは実行できる単なる関数ですが、クラス インスタンスは多くのメソッドを提供する可能性があるためです。
クロージャを使用する場合
実際、クロージャは Python では非常に一般的ですが、これがクロージャであるという事実に特別な注意を払っていなかっただけです。たとえば、Python のデコレータでは、パラメータを使用してデコレータを記述する必要がある場合、通常はクロージャが生成されます。
なぜでしょうか? Python のデコレータは固定関数インターフェイス形式だからです。デコレータ関数 (またはデコレータ クラス) が関数を受け入れて関数を返す必要があります:
# how to define def wrapper(func1): # 接受一个callable对象 return func2 # 返回一个对象,一般为函数 # how to use def target_func(args): # 目标函数 pass # 调用方式一,直接包裹 result = wrapper(target_func)(args) # 调用方式二,使用@语法,等同于方式一 @wrapper def target_func(args): pass result = target_func()
それでは、デコレータがパラメータを受け取る場合はどうなるでしょうか? 次に、これらのパラメータを受け取るために使用される、元のデコレータ上に別のレイヤーをラップする必要があります。これらのパラメータ (プライベート グッズ) が内部デコレータに渡された後、クロージャが形成されます。したがって、デコレータがカスタム パラメータを必要とする場合、通常はクロージャが形成されます。 (クラス デコレータは例外)
def html_tags(tag_name): def wrapper_(func): def wrapper(*args, **kwargs): content = func(*args, **kwargs) return "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content) return wrapper return wrapper_ @html_tags('b') def hello(name='Toby'): return 'Hello {}!'.format(name) # 不用@的写法如下 # hello = html_tag('b')(hello) # html_tag('b') 是一个闭包,它接受一个函数,并返回一个函数 print hello() # <b>Hello Toby!</b> print hello('world') # <b>Hello world!</b>
デコレータのより詳細な分析については、私が書いた別のブログを読むことができます。
もう少し詳しく見てみましょう
実際、上記の概念を理解していれば、頭痛の種と思われる多くのコードはそれだけで済みます。
クロージャ パッケージがどのようなものかを見てみましょう。実際、クロージャ関数には通常の関数と比較して追加の __closure__ 属性があり、すべてのセル オブジェクトを格納するタプルを定義し、各セル オブジェクトはすべての外部変数を 1 つずつクロージャに格納します。
>>> def make_printer(msg1, msg2): def printer(): print msg1, msg2 return printer >>> printer = make_printer('Foo', 'Bar') # 形成闭包 >>> printer.__closure__ # 返回cell元组 (<cell at 0x03A10930: str object at 0x039DA218>, <cell at 0x03A10910: str object at 0x039DA488>) >>> printer.__closure__[0].cell_contents # 第一个外部变量 'Foo' >>> printer.__closure__[1].cell_contents # 第二个外部变量 'Bar'
原理はとてもシンプルです。