クローズド

10702 ワード

Pythonを学ぶ時、自分がJavaScriptの基礎があることを喜んで、学習の過程の中で、多くの類似の地方を発見して、例えばパケットを導く方式、パラメータの解包、lambdaの表現式、閉包、引用の賦値、関数はパラメータとしてなどです.装飾器はPython言語の一つのポイントであり、装飾器の使用を学ぶには、まず閉包から見て、装飾器の基本原理を把握しなければならない.
ワードスコープ
閉パケットは関数がその存在する文法的役割ドメインにアクセスして記憶できるものであり,PythonもJavaScriptも文法的役割ドメインに基づいているため,両者の閉パケットの概念は共通している.Pythonが動的役割ドメインではなく文法的役割ドメインであることを説明するために、次の例を見ることができます.
def getA():
    return a

def test():
    a = 100
    print(getA())

test()

実行結果:
Traceback (most recent call last):
  File "C:\Users\Charley\Desktop\py\py.py", line 8, in 
    test()
  File "C:\Users\Charley\Desktop\py\py.py", line 6, in test
    print(getA())
  File "C:\Users\Charley\Desktop\py\py.py", line 2, in getA
    return a
NameError: name 'a' is not defined

エラーが発生しました.getA関数が存在する役割ドメインで変数aを宣言します.
def getA():
    return a
    
a = 10010
def test():
    a = 100
    print(getA())

test()

実行結果:
10010

ここで10010が出力され、getA関数が文法的役割ドメインに依存していることを示し、その役割ドメインは関数定義の初めから決定され、その役割ドメインは呼び出し位置の影響を受けない.文法的役割ドメインを理解し,閉パッケージを理解した.
クローズドパッケージ
前述したように,閉パケットは関数がその存在する文法的役割ドメインにアクセスし記憶できる特性である.では、関数がこの特性を持っている限り、この関数は閉パケットであり、理論的には、すべての関数は閉パケットであり、彼らが存在する文法の役割ドメインにアクセスできるためです.このような関数も閉パッケージです.
a = 100
def iAmClosure():
    print(a)

iAmClosure()

理論的にはそうですが、実際には、閉パケットの定義は少し複雑ですが、前の理論に基づいています.1つの関数(外層関数)で別の関数(内層関数)を返すと、内層関数が外層関数が存在する役割ドメインにアクセスできるので、閉パケットと呼ばれます.次に例を示します.
def outer():
    a = 100
    def inner():
        print(a)
    return inner

outer()()

実行結果は次のとおりです.
100

上記のように、inner関数は閉パケットです.
閉パッケージでパラメータ関数を呼び出す
JavaScriptと同様に、Pythonでは関数をパラメータとして渡すこともできます.Pythonは参照伝達値であるため、実際に伝達されるパラメータは元の関数自体ではなく、元の関数の参照です.閉パッケージの特性に基づいて、閉パッケージでこの関数にアクセス(または呼び出す)することもできます.
def outer(fn):
    def inner():
        #                   
        #            fn   
        fn()
    return inner

def test():
    print("We will not use 'Hello World'")

ret = outer(test)
ret()

実行結果:
We will not use 'Hello World'

アクセサリー導入
閉包を知ったら、装飾器を話してもいいです.このような需要があることを想像してみてください.あなたの会社にはいくつかの核心的な底辺の方法があります.その後、会社は徐々に大きくなり、他の部門を増やしました.これらの部門には自分の業務がありますが、これらの核心的な底辺の方法を使用しています.あなたの部門もそうです.ある日、プロジェクトマネージャがあなたを見つけて、コアコードに検証などを加えるようにしましたが、コアコードを修正することはできません(そうしないと他の部門に影響します).どうすればいいですか?まず、この方法を考えるかもしれません.
def core():
    pass

def fixCore():
    doSometing()...
    core()

fixCore()
core関数を1つの外層関数でパッケージし、検証機能が実行された後、core関数を呼び出す.この時、プロジェクトマネージャはまた、私たちの呼び出し方法を変えることはできません.私たちはcoreで呼び出したいと思っています.そこで、コードを変更しました.
def core():
    pass

tmp = core;
def fixCore():
    tmp()

core = fixCore
core()

一時関数tmpcorefixCoreを交換し、タヌキは太子を交換した.これでcoreを楽しくそのまま使えます.これはプロジェクトマネージャがまた言ったことです.私たちは複数のコア関数を包装する必要があります.変数をすべて交換することはできません.そして、これは優雅ではありません.他の方法を考えてみましょう.はい、要求が多いですね.そこで考えてみると、パッケージを閉じる方法を考えました.パッケージする必要がある関数をパラメータとして外層関数に入力し、外層関数はいくつかの検証操作を実行した後、パッケージ関数を呼び出す内層関数を返します.このようなメリットは、
  • は、関数をパラメータとして外層関数
  • に伝達する限り、任意の関数をパッケージすることができる.
  • は、外層関数を実行するときに、戻り値に任意の名前を付けることができる
  • .
    あなたが書いたコードはこうです.
    #     ,            
    def outer(fn):
        def inner():
            doSometing()...
            fn()
        return inner
    
    #     1
    def core1():
        pass
    
    #     2
    def core2():
        pass
    
    # core1            
    core1 = outer(core1)
    # core2            
    core2 = outer(core2)
    
    #       
    core1()
    core2()
    

    大成功!完璧です.同時におめでとうございます.あなたはすでに装飾器を実現しました.装飾器の核心原理は:閉包+関数実参です.
    Pythonオリジナルアクセサリーサポート
    上では簡単な装飾器を実現し、装飾器の基本原理も知っています.実は、Python言語では、装飾器に対するオリジナルのサポートがありますが、核心原理は依然として変わらず、私たちの操作を簡略化しています.
    #     ,            
    def outer(fn):
        def inner():
            print("----   ----")
            fn()
        return inner
    
    #      
    @outer
    #     1
    def core1():
        print("----core1----")
    #      
    @outer
    #     2
    def core2():
        print("----core2----")
    
    core1()
    core2()
    

    実行結果は次のとおりです.
    ----   ----
    ----core1----
    ----   ----
    ----core2----
    

    Pythonオリジナルのアクセラレータサポートでは、パラメータの変更や変更の手順を省き、アクセラレータを適用すると、アクセラレータの下の関数(ここではcore1core2)をパラメータとして、元の関数を上書きする新しい関数を生成します.
    アクセラレータ関数の実行タイミング
    アクセサリー関数(つまり、前に述べた外層関数)はいつ実行されますか?簡単な検証が可能です.
    def outer(fn):
        print("----       ----")
        def inner():
            print("----   ----")
            fn()
        return inner
    
    @outer
    def core1():
        print("----core1----")
    
    @outer
    def core2():
        print("----core2----")
    

    実行結果:
    ----       ----
    ----       ----
    

    ここではcore1core2の関数を直接呼び出しておらず,装飾器関数が実行された.すなわち,解釈器実行中に装飾器に遭遇すると,装飾器関数が実行される.
    たじゅうかざり
    関数に複数のアクセサリを適用することもできます.
    def outer1(fn):
        def inner():
            print("----outer1    ----")
            fn()
        return inner
    
    def outer2(fn):
        def inner():
            print("----outer2    ----")
            fn()
        return inner
    
    @outer2
    @outer1
    def core1():
        print("----core1----")
    
    core1()
    

    実行結果は次のとおりです.
    ----outer2    ----
    ----outer1    ----
    ----core1----
    

    出力効果から、デコライザの実行は下から上へ、下位デコライザの実行が完了した後に関数を返して上位のデコライザに渡されることがわかります.
    被装飾関数にパラメータを渡す
    被装飾関数にパラメータを渡す必要がある場合は、被装飾関数が返すinner関数に文章を書き、そのエージェントに被装飾関数のパラメータを受け入れさせ、被装飾関数に渡す必要があります.
    def outer(fn):
        def inner(*args,**kwargs):
            print("----outer    ----")
            fn(*args,**kwargs)
        return inner
    
    
    @outer
    def core(*args,a,b):
        print("----core1----")
        print(a,b)
    
    core(a = 1,b = 2)
    

    実行結果:
    ----outer    ----
    ----core1----
    1 2
    

    ここで、パラメータ解包の問題について説明する.innerの関数の*および**は、その関数が受け入れる可変パラメータおよびキーワードパラメータを表し、パラメータ関数fnを呼び出すときに*および**は、JavaScriptの拡張演算子...と同様に、可変パラメータおよびキーワードパラメータを解包することを表す.argsおよびkwargsを直接パラメータとして被装飾関数に渡す場合、被装飾関数は1つのメタセットおよび辞書のみを受信するので、パケットを解いた後に伝達する必要がある.
    戻り値のある関数をパッケージ化
    被包装関数に戻り値がある場合、どのようにして包装で戻り値を取得しますか?まず次の例を見てみましょう.
    def outer(fn):
        def inner():
            print("----outer    ----")
            fn()
        return inner
    
    @outer
    def core():
        return "Hello World"
    
    print(core())
    

    実行結果:
    ----outer    ----
    None
    

    なぜ関数実行の戻り値はNoneなのでしょうか.Hello Worldではないでしょうか.これは装飾の過程が実は置換の過程を引用するためで、装飾の前で、core変数はその初期の関数体から指向して、装飾の後で再び指向して、装飾器の関数が返したinner関数に指向して、私達はinner関数に定義の戻り値を与えていないで、装飾を呼び出したcore関数も、自然に戻り値がありません.装飾後の関数に戻り値を残すには、inner関数を装飾前の関数の戻り値に戻すだけです.
    def outer(fn):
        def inner():
            print("----outer    ----")
            return fn()
        return inner
    
    @outer
    def core():
        return "Hello World"
    
    print(core())
    

    実行結果は次のとおりです.
    ----outer    ----
    Hello World
    

    アクセサリーのパラメータ
    場合によっては、関数を異なる状況で装飾したい場合があります.次の2つの処理方法があります.
  • は、複数の異なる条件における装飾器を定義し、条件に応じて異なる装飾器
  • を適用する.
  • は、条件によって装飾
  • を装飾器内部で行う装飾器を定義する.
    1つ目の方法は簡単ですが、ここでは2つ目の方法について説明します.装飾器の内部で異なる条件を判断するには、パラメータを入力する1つ以上のパラメータが必要です.
    # main       ,              
    def main(flag):
        # flag   True
        if flag:
            def outer(fn):
                def inner():
                    print("   Flag")
                    fn()
                return inner
            return outer
        # flag   False
        else:
            def outer(fn):
                def inner():
                    print("Flag    !")
                    fn()
                return inner
        return outer
    
    #   main      True   
    @main(True)
    def core1():
        pass
    
    #   main      False   
    @main(False)
    def core2():
        pass
    
    core1()
    core2()
    

    実行結果は次のとおりです.
       Flag
    Flag    !
    
    mainに異なるパラメータを入力することに基づいて、core1およびcore2関数に異なる装飾器を適用した.ここでのmain関数は装飾関数ではなく,その戻り値こそ装飾関数であり,main関数の戻り値に基づいてターゲット関数を装飾した.
    類を装飾器とする
    関数に加えて、クラスは装飾器としてもよいが、クラスを装飾器として言う前に、まず__call__の方法を理解する必要がある.
    __call__ 方法
    作成したインスタンスも呼び出すことができます.インスタンスオブジェクトを呼び出すと、その内部の__call__メソッドが実行されます.このメソッドは手動で実装する必要があります.このメソッドがなければ、インスタンスは呼び出されません.
    class Test(object):
        def __call__(self):
            print("      ")
    
    t = Test()
    t()
    

    実行結果:
          
    

    類を装飾器とする
    オブジェクトの__call__メソッドは、オブジェクトが呼び出されたときに実行されることが知られているが、クラスがデコレーションの結果としてそのオブジェクトにデコレーションされた関数を指し、そのオブジェクトが呼び出されたときにオブジェクトの__call__メソッドを実行し、デコレーションされた関数に__call__メソッドを実行させるには、まずオブジェクトが作成され、したがって、__new__メソッドと__init__メソッドが連動し、オブジェクトの作成時にtest関数がパラメータ伝達オブジェクトの__init__メソッドとして扱われます.
    class Test(object):
        #    __new__   
        def __new__(self,oldFunc):
            print("__new__     ")
            return object.__new__(self)
        #    __init__   
        def __init__(self,oldFunc):
            print("__init__     ")
        #    __call__   
        def __call__(self):
            print("      ")
    
    #        
    @Test
    def test():
        print("  test  ~~")
    
    test()
    

    実行結果:
    __new__     
    __init__     
          
    

    元の装飾関数を保存
    装飾後のtest関数は新しいオブジェクトを指していますが、装飾される前の元の関数を保存する方法はありますか?前述したように、オブジェクトを新規作成するときに、装飾された関数が__new__メソッドと__init__メソッドにパラメータとして渡されるため、この2つのメソッドで元の関数の参照を取得することができます.
    class Test(object):
        #    __new__   
        def __new__(self,oldFunc):
            print("__new__     ")
            return object.__new__(self)
        #    __init__   
        def __init__(self,oldFunc):
            print("__init__     ")
            self.__oldFunc = oldFunc
        #    __call__   
        def __call__(self):
            print("      ")
            self.__oldFunc()
    
    #        
    @Test
    def test():
        print("  test  ~~")
    
    test()
    

    実行結果は次のとおりです.
    __new__     
    __init__     
          
      test  ~~
    

    まとめ
    本文は主にPythonの中の閉包と装飾器の概念を述べて、主に以下の内容があります:
  • Pythonは文法に基づくドメイン
  • である.
  • 閉パケットは、関数が記憶してアクセスできる文法的役割ドメイン
  • である.
  • ギフトバッグによるシンプルなデコレーション
  • を実現
  • Pythonオリジナルアクセサリー対応
  • 関数に複数の装飾器
  • を適用する
  • 被装飾関数に
  • をどのように伝達するか
  • 戻り値のある関数にデコレーション
  • を適用する方法
  • 異なる条件に従って関数に異なる装飾器
  • を適用する方法
  • 類を装飾器とする場合及び__call__方法
  • .
    終わります.