Pythonのアクセサリーを素早く知る

6213 ワード

理解する必要があるいくつかの概念
Pythonの中の装飾器を理解するには、やはり最も基本的な概念から始めるべきだと思います.
デコレーションモード:デコレーションモードとは、「既存の内部実装を変更することなく、関数やクラスに何らかの特性を追加する」と簡単に理解できます.これにより、ビジネスに関係なく汎用性のあるコードを抽象化し、これらのコードを必要とする関数やクラスに装飾器として付加することができます.切面向けプログラミングの考え方で「装飾器は切面であるべきだ」と説明した.
関数は一等公民です.関数は普通の変数として使用できるという意味です.Pythonでは、関数を変数に付与したり、関数を他の関数のパラメータにしたり、関数を他の関数の戻り値にしたりすることができます.
閉包:ローカル役割ドメインがグローバル役割ドメインの変数を参照できることはよく知られています.同様に、1つの関数の内部に他の関数が定義されている場合、内部関数は外部関数が存在する役割ドメインの変数を使用することができます.これが閉包です.
最も簡単な装飾器から始めます
以上の概念を理解した後,これらの特性を用いて簡単な装飾器を実現することを試みた.
まず、必要性を明確にすると、関数呼び出し時に対応するログを印刷する必要がある場合があります.印刷ログが必要なすべての関数コードに印刷ログのコードを埋め込むことで実現できますが、この方法は多くの重複コードを増加させるだけでなく、ビジネスコードにビジネスに関係のないコードを埋め込むことで全体的な結合度を増加させます.したがって、関数呼び出し時にログ記録関数呼び出し動作を印刷できる装飾器を実現する必要があります.
次の関数fooがあれば、具体的なビジネス関数を表します.

def foo():
  print('in function foo')

foo=deco(foo)を呼び出すことで,関数fooに印刷ログを追加する機能を実現し,従来の業務に影響を及ぼさないことを想定した.このような想定の下で、装飾器decoも入力として別の関数を受信し、新しい装飾された関数を返す関数であるべきである.Pythonでは、次のように書くことができます.

def deco(func): #           
  def new_func():
    print(f'[log] run function {func.__name__}') #      Python3.6       
    func() #   ,                
  return new_func #            

試し効果を実行します.

>>> foo = deco(foo)
>>> foo()
[log] run function foo
in function foo

いいですね.これで私たちは最も簡単な装飾器を実現しました.上記のコードでは、アクセラレータdecoは任意の関数をパラメータとして受信し、内部に別の関数を構築し、閉パケットの特性を利用して、新しい関数でアクセラレータdecoの局所的な役割ドメインに存在する関数funcを呼び出すことができる.
不思議な@
上記のように書くと、装飾が必要な関数に新しい値を割り当てるたびに、万一関数や装飾器の数が増えたら、手動で値を書くことと関数呼び出しが非常に面倒になります.ではPythonでは、もっと優雅な書き方はありませんか?答えはあります.@記号が1つしか必要ありません.
Pythonでは、アクセサリーが必要な場合:

def deco(func):
  def new_func():
    print(f'[log] run function {func.__name__}')
    func()
  return new_func

@deco
def foo():
  print('in function foo')

ここでは関数の付与値を省略し,関数foo定義の前の行に@decoを付けて直接装飾する.実行してみます.

>>> foo()
[log] run function foo
in function foo

不思議な感じがしますか?実はこの中には魔法はありません.Pythonが関数定義のコードを処理するときに、foo=deco(foo)の論理を加えただけです.
アクセサリーもパラメータ欲しい
上のコードは、ビジネス関数の呼び出し前にログを印刷する機能を実現しています.ビジネスコードの実行後にカスタムメッセージを印刷する必要がある場合は、どうすればいいですか?カスタムパラメータをアクセサリで受信できるようにする必要があります.
前述したように、Pythonは@decoと書いたときにdeco(func)を呼び出した結果を装飾する関数に割り当てるだけです.この論理に従って、パラメータ付きの装飾器が必要な場合、コードに@deco('some message')と書くべきで、Pythonはdeco(msg)(func)を呼び出した結果をfooに割り当てます.では、簡単になります.上のコードに基づいて関数をネストするだけです.

def deco(msg):
  def inner_deco(func):
    def new_func():
      print(f'[log] run function {func.__name__}')
      func()
      print(f'[log] {msg}')
    return new_func
  return inner_deco

@deco('some message')
def foo():
  print('in function foo')

実行してみます.

>>> foo()
[log] run function foo
in function foo
[log] some message

パラメータ付きの被装飾関数をサポートしない装飾器は良い装飾器ではありません
上のコードはまだ問題があります.私たちは関数fooにパラメータがない場合だけを考慮しています.もし関数fooがパラメータを持っていたら、この装飾器はパラメータ情報を失います.これは合格した装飾器が現れるべき状況ではありません.したがって、Pythonの*argsと*kwargsを使用して、装飾された関数を任意のパラメータに転送することをサポートします.

def deco(msg):
  def inner_deco(func):
    def new_func(*args, **kwargs):
      print(f'[log] run function {func.__name__}')
      func(*args, **kwargs)
      print(f'[log] {msg}')
    return new_func
  return inner_deco

@deco('some message')
def foo(a, b=None):
  print('in function foo')
  print(f'a is {a} & b is {b}')

これにより、関数fooのパラメータリストがどのようになっても問題はありません.

>>> foo('hello')
[log] run function foo
in function foo
a is hello & b is None
[log] some message

戻り値のある被装飾関数をサポートしない装飾器は良い装飾器ではありません
忘れないでください.今まで書いた関数fooには戻り値がありませんでしたが、関数fooに戻り値があればどうしますか.あなたの心には答えがあると思います.

def deco(msg):
  def inner_deco(func):
    def new_func(*args, **kwargs):
      print(f'[log] run function {func.__name__}')
      rlt = func(*args, **kwargs)
      print(f'[log] {msg}')
      return rlt
    return new_func
  return inner_deco

@deco('some message')
def foo(a, b=None):
  print('in function foo')
  print(f'a is {a} & b is {b}')
  return 'ok'

アクセラレータは元の関数が実行された後に別の操作があるため、戻り値を一時的に保存し、アクセラレータの論理が実行されるまで最終結果を返さなければならない.これが私たちの最終版の装飾器です!

>>> rlt = foo('a')
[log] run function foo
in function foo
a is a & b is None
[log] some message
>>> print(rlt)
ok

もっと騒々しい操作はありますか?
もちろんありますよ.私のタイトルをこのように書いたのは難しいですか?
Pythonでは、アクセサリーとしてクラスを使用できます.

class Deco(object):
  def __call__(self, func):
    def new_func():
      print(f'[log] run function {func.__name__}')
      func()
    return new_func

@Deco()
def foo():
  print('in function foo')

>>> foo()
[log] run function foo
in function foo

このような利点は、クラスを利用してパラメータと呼び出しロジックをよりよく管理できることであり、以前の3層関数のネストされた形式よりもずっとはっきりしているのではないでしょうか.
Pythonでは、アクセサリーを使ってクラスを飾ることもできます.例えば、

def add_doc(doc):
  def deco(cls):
    cls.__doc__ = doc
    return cls
  return deco

@add_doc('this is the doc of Cls')
class Cls(object):
  pass

効果を見てみましょう.

>>> help(Cls)
Help on class Cls in module __main__:

class Cls(builtins.object)
 | this is the doc of Cls

上のコードはただの例で、装飾器がどのようにクラスを装飾するかを示しているだけで、実際にそう使うべきではありません.ほとんどの場合、クラスの拡張は装飾ではなく継承によって行われるべきです.
具体的にどのように装飾器を巧みに利用するかは、みんなが想像力を発揮しなければならない.
まとめ
以上、Pythonのデコレーションのすべてをすばやく理解することについて、皆さんの役に立つことを願っています.興味のある方は引き続き当駅の他の関連テーマを参照することができます.不足点があれば、伝言を歓迎します.友达の本駅に対する支持に感谢します!