pythonアクセサリーの概要---この1編で十分かもしれません

21262 ワード

Python装飾器(decorator)はプログラム開発でよく使われる機能で、装飾器を合理的に使うことで、私たちのプログラムを虎に翼を添えることができます.
アクセサリー導入
初期及び問題の誕生
もし今1つの会社で、A B Cの3つの業務部門があって、Sの1つの基礎サービス部門があって、現在、S部門は2つの関数を提供して、他の部門に呼び出して、関数は以下の通りです:
def f1():
    print('f1 called')


def f2():
    print('f2 called')

初期には、他の部門がこのように呼び出すのは問題なく、会社の業務の発展に伴い、S部門は関数呼び出しに対して権限検証が必要であり、権限があれば呼び出しを行うことができ、そうでなければ呼び出しに失敗する.私たちならどうすればいいか考えてみましょう.
シナリオセット
  • 呼び出し元であるABC部門が呼び出すときに、まず権限検証
  • を自発的に行うようにする.
  • S部門は、対外的に提供する関数のうち、まず権限認証を行い、その後、本格的な関数操作
  • を行う.
    に質問
  • 案1は、外層に露出すべきではない権限認証を、使用面に暴露する前に、同時に複数の部門がある場合は、各部門の誰もが知っておく必要があります.他の人が必ずそうする必要はありません.頼りになりません.
  • 案2、行を見ているように見えますが、S部門が対外的により多くの権限検証方法を提供する場合、各関数は権限検証を呼び出す必要があります.同様に苦労して、コードのメンテナンス性と拡張性に不利です.
    では、コードのオープンクローズの原則に従って、この問題を完璧に解決する方法はありますか?
    アクセサリー導入
    答えはあるに違いない.さもないと本当に弱い.まずコードを見て
    def w1(func):
        def inner():
            print('...    ...')
            func()
    
        return inner
    
    
    @w1
    def f1():
        print('f1 called')
    
    
    @w1
    def f2():
        print('f2 called')
    
    
    f1()
    f2()

    出力結果は
    ...    ...
    f1 called
    ...    ...
    f2 called

    コードおよび出力から,f 1 f 2関数を呼び出す際に権限検証に成功したことが分かるが,どうすればよいのだろうか.実はここでは装飾器を用いて、閉パッケージ関数w 1を定義することによって、私たちが関数を呼び出す上でキーワード@w 1を通じて、f 1 f 2関数に対して装飾を完成しました.
    装飾器の原理
    まず、私たちの装飾器関数w 1を見てみましょう.この関数はパラメータfuncを受信します.実はメソッド名を受信します.w 1の内部には関数innerを定義し、inner関数に権限チェックを追加し、権限を検証した後に伝達されたパラメータfuncを呼び出します.同時に、w 1の戻り値は内部関数innerで、実は閉包関数です.
    それから、もう一度見て、f 1に@w 1を追加します.それはどういう意味ですか.python解釈器がこの文を実行すると、w 1関数が呼び出され、同時に装飾された関数名がパラメータとして伝達する(この場合f 1)、閉パケット一文分析によれば、w 1関数を実行する際、直接inner関数が戻るとともにf 1に付与され、このときのf 1は装飾されていない時のf 1ではなく、w 1を指す.inner関数アドレス.
    次に、f 1()を呼び出すとき、実はw 1を呼び出す.Inner関数では、このとき権限検証を実行してから元のf 1()を呼び出し、そこのf 1は装飾によって渡されたパラメータf 1である.
    このようにしてf 1の装飾が完了し,権限検証が実現される.
    アクセサリーの知識点
    実行タイミング
    デコレーションの原理を知って、その実行タイミングはどうなっているのか、次に見てみましょう.国際慣例、先にコードをつける
    def w1(fun):
        print('...       ...')
    
        def inner():
            print('...    ...')
            fun()
    
        return inner
    
    
    @w1
    def test():
        print('test')
    
    test()

    出力結果は
    ...       ...
    ...    ...
    test

    これにより、python解釈器が@w 1に実行されると、以下のコードが実行されることに相当する装飾が開始されることが分かった.
    test = w1(test)

    2つの装飾品実行プロセスと装飾結果
    2つ以上の装飾器が1つの関数を装飾している場合、実行フローと装飾結果はどのようなものですか?同様に、問題をコードで説明します.
    def makeBold(fun):
        print('----a----')
    
        def inner():
            print('----1----')
            return '' + fun() + ''
    
        return inner
    
    
    def makeItalic(fun):
        print('----b----')
    
        def inner():
            print('----2----')
            return '' + fun() + ''
    
        return inner
    
    
    @makeBold
    @makeItalic
    def test():
        print('----c----')
        print('----3----')
        return 'hello python decorator'
    
    
    ret = test()
    print(ret)

    出力結果:
    ----b----
    ----a----
    ----1----
    ----2----
    ----c----
    ----3----
    <b><i>hello python decorator</i></b>

    まず第2のアクセラレータ(makeItalic)で装飾し、次に第1のアクセラレータ(makeBold)で装飾し、呼び出し中に第1のアクセラレータ(makeBold)を先に実行し、次に第2のアクセラレータ(makeItalic)を実行することが分かる.
    なぜか、2つのステップに分けて分析してみましょう.
  • 装飾タイミング上記装飾タイミングの紹介により、@makeBoldまで実行する場合、以下の関数を装飾する必要があることがわかります.このとき解釈器は下に進み続け、関数名ではなく装飾器であることがわかります.この場合、@makeBold装飾器は実行を一時停止し、次の装飾器@makeItalicを実行します.次にtest関数名をデザイナ関数に入力して’b’を印刷し、makeItalicデザイナが完了すると、そのときのtestがmakeItalicのinner関数アドレスを指し、そのときに戻って@makeBoldを実行し、次に新しいtestをmakeBoldデザイナ関数に入力するので‘a’を印刷した.
  • test関数を呼び出すとき、上記の解析による、このときtestはmakeBoldを指す.inner関数なので、まず‘1’,次にfun()を呼び出すときは、実は呼び出されたmakeItalic.inner()関数なので「2」を印刷しmakeItalic.Innerでは、呼び出されたfunこそが私たちの最もオリジナルのtest関数なので、オリジナルのtest関数の「c',‘3'」を印刷するので、1層ずつ調整した後、印刷の結果はhello python decoratorです.

  • 無参関数の装飾
    上記の例のf 1 f 2はいずれも無パラメトリック関数の装飾であり、単独では例を挙げない
    パラメトリック関数の装飾
    使用中にパラメータが付いている関数もあるかもしれませんが、これはどのように処理しますか?コード優先:
    def w_say(fun):
        """
                ,               ,           
        """
    
        def inner(name):
            """
                       ,           
            :param name:
            :return:
            """
            print('say inner called')
            fun(name)
    
        return inner
    
    
    @w_say
    def hello(name):
        print('hello ' + name)
    
    
    hello('wangcai')

    出力結果:
    say inner called
    hello wangcai

    具体的な説明コードの注釈はすでにあり、単独で説明しません.このとき、それはパラメータで、複数または不定長パラメータであれば、どのように処理すればいいのかと聞かれるかもしれません.次のコードを見てください.秒で分かります.
    def w_add(func):
        def inner(*args, **kwargs):
            print('add inner called')
            func(*args, **kwargs)
    
        return inner
    
    
    @w_add
    def add(a, b):
        print('%d + %d = %d' % (a, b, a + b))
    
    
    @w_add
    def add2(a, b, c):
        print('%d + %d + %d = %d' % (a, b, c, a + b + c))
    
    
    add(2, 4)
    add2(2, 4, 6)

    出力結果:
    add inner called
    2 + 4 = 6
    add inner called
    2 + 4 + 6 = 12

    pythonの可変パラメータを用いて,装飾テープパラメータの関数を容易に実現した.
    戻り値付き関数の装飾
    次に、戻り値のある関数を飾ります.前の書き方では、コードはこうです.
    def w_test(func):
        def inner():
            print('w_test inner called start')
            func()
            print('w_test inner called end')
        return inner
    
    
    @w_test
    def test():
        print('this is test fun')
        return 'hello'
    
    
    ret = test()
    print('ret value is %s' % ret)

    出力結果:
    w_test inner called start
    this is test fun
    w_test inner called end
    ret value is None

    この場合、test関数の「hello」が出力されていないのではなく、Noneであることがわかります.それはなぜでしょうか.inner関数でtestを呼び出したのですが、戻り値を受け入れられず、戻りも行われていません.デフォルトはNoneです.原因がわかりました.では、コードを修正します.
    def w_test(func):
        def inner():
            print('w_test inner called start')
            str = func()
            print('w_test inner called end')
            return str
    
        return inner
    
    
    @w_test
    def test():
        print('this is test fun')
        return 'hello'
    
    
    ret = test()
    print('ret value is %s' % ret)

    出力結果:
    w_test inner called start
    this is test fun
    w_test inner called end
    ret value is hello

    これにより,戻り値パラメータ付き関数の装飾が完了すると予想される.
    パラメータ付き装飾器
    パラメータ付き関数と戻り値のある関数を装飾することを紹介しましたが、パラメータ付き装飾器はありますか?もしあれば、何の役に立ちますか?答えはあるに違いないので、次はコードで見てみましょう.
    def func_args(pre='xiaoqiang'):
        def w_test_log(func):
            def inner():
                print('...    ...visitor is %s' % pre)
                func()
    
            return inner
    
        return w_test_log
    
    
    #                 ,      
    
    #    func_args('wangcai'),  w_test_log     
    # @w_test_log
    #   @w_test_log test_log    
    @func_args('wangcai')
    def test_log():
        print('this is test log')
    
    
    test_log()

    出力結果:
    ...    ...visitor is wangcai
    this is test log

    簡単に理解すると、パラメータ付きの装飾器は元の閉包に加えて閉包し、外層関数func_を通じてargsの戻り値w_test_logは見て、具体的な実行プロセスは注釈の中ですでに説明しました.利点は,実行時に異なるパラメータに対して異なる応用機能処理を行うことである.
    ユニバーサルデコレーション
    こんなにたくさん紹介しましたが、実際の応用では、カテゴリのない関数に対して装飾器を1つ書くと、疲れてしまうと思います.では、汎用万能装飾器があるかどうか、答えはあるに違いありません.くだらないことは言わないで、直接コードをつけます.
    def w_test(func):
        def inner(*args, **kwargs):
            ret = func(*args, **kwargs)
            return ret
    
        return inner
    
    
    @w_test
    def test():
        print('test called')
    
    
    @w_test
    def test1():
        print('test1 called')
        return 'python'
    
    
    @w_test
    def test2(a):
        print('test2 called and value is %d ' % a)
    
    
    test()
    test1()
    test2(9)

    出力結果:
    test called
    test1 called
    test2 called and value is 9 

    上記のいくつかの例を組み合わせると、汎用装飾器の機能が完成し、原理は同じで、くだらない話にすぎない.
    クラス装飾器
    アクセラレータ関数は、パラメータとしてcallableオブジェクトを受け入れ、callableオブジェクトを返すインタフェースコンストレイントです.pythonでは、一般的にcallableオブジェクトは関数ですが、例外もあります.たとえば、あるオブジェクトがcallメソッドを書き換えると、このオブジェクトはcallableになります.
    オブジェクトを作成した後、このオブジェクトを直接実行すると、callableではなく、直接実行できないため、例外が放出されますが、変更すると、呼び出しを直接実行できます.次のようになります.
    class Test(object):
        def __call__(self, *args, **kwargs):
            print('call called')
    
    
    t = Test()
    print(t())

    出力:
    call called

    次に、本題を導入して、クラス装飾関数の使い方を見てみましょう.
    class Test(object):
        def __init__(self, func):
            print('test init')
            print('func name is %s ' % func.__name__)
            self.__func = func
    
        def __call__(self, *args, **kwargs):
            print('       ')
            self.__func()
    
    
    @Test
    def test():
        print('this is test func')
    
    
    test()
    

    出力結果:
    test init
    func name is test 
           
    this is test func

    従来の原理と同様にpythonインタプリタが@Testに実行されると、現在のtest関数がパラメータとしてTestオブジェクトに渡され、initメソッドが呼び出され、同時にtest関数が作成されたTestオブジェクトに指し示すと、次にtest()が実行されると、実際には作成されたオブジェクトを直接呼び出し、そのcallメソッドが実行される.
    はい、今までpythonアクセサリーと関連知識点を基本的に話しましたが、問題があれば、指摘してください.