Pythonデコレータ

Pythonデコレータ#

デコレータの紹介##

ログを印刷してこの関数の呼び出しステータスを出力するなど、関数の実行の前後に他のコードを実行する場合はどうなりますか?

#! /usr/bin/env python
# coding=utf-8

def logger(fn):									#パラメータとして機能します。fnは任意のパラメータにすることができます
 def wrap(*args,**kwargs):					#可変パラメーターargsおよびkwargs
  print('call {}'.format(fn.__name__))	
  ret =fn(*args,**kwargs)				#関数呼び出し中のパラメーターの分解
  print('{} called'.format(fn.__name__))return ret								#関数の戻り値を返します
 return wrap

def add(x, y):return x + y

logger_add =logger(add)print(logger_add.__name__)print(logger_add)
ret =logger_add(3,5)print(ret)

# 出力結果:
wrap
< function logger.<locals>.wrap at 0x7fba35f4fe18>
call add
add called
8

この効果を実現するには、次の方法を使用することもできます

@ logger                                                                                  
def add(x, y):return x + y                                                                         ret =add(3,5)print(ret) 

# 出力結果:
call add
add called
8

これはPythonデコレータの簡単な使用法です

デコレータとは何ですか?

デコレータは、ソフトウェアの設計パターンに使用される名前です。デコレータは、サブクラスを直接使用したり、装飾された関数のソースコードを変更したりすることなく、関数、メソッド、またはクラスの関数を動的に変更できます。 Pythonデコレータは、Python構文に対する特別な変更であり、関数、メソッド、およびクラスをより便利に変更できるようにします。

次のようにコードを書くとき:

@ logger
def add(x, y):...

これは、次の手順を個別に実行することと同じです。

def add(x, y):...
logger_add =logger(add)

デコレータ内のコードは通常、 * args ** kwargsを使用して任意のパラメータを受け入れる新しい関数を作成します。上記のコードのwrap()関数は次のようになります。この関数内で、元の入力関数(デコレータの入力パラメータであるラップされた関数)を呼び出して、その結果を返す必要があります。ただし、上記のコードで関数呼び出しを出力したり、タイミング処理を追加したりするなど、追加したいコードを追加することもできます。この新しく作成されたラップ関数は、元の関数を置き換えて、デコレータの結果として返されます。

したがって、** Pythonでは、デコレータのパラメータは関数であり、戻り値は関数の関数です**。

デコレータの例:タイミング処理##

関数の実行時間を計算するためのデコレータを作成します

import time

def timethis(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(fn.__name__, end - start)return ret
 return wrap

追加機能の時間を計りたい場合:

@ timethis
def add(x, y):return x + y

ret =add(3,5)print(ret)

# 出力結果
add 1.9073486328125e-068

スリープ機能の時間を計りたい場合:

@ timethis
def sleep(x):
 time.sleep(x)sleep(3)

# 出力結果
sleep 3.003262519836426

装飾された関数のメタ情報を保存します##

関数のメタ情報は何ですか###

たとえば、デコレータの名前、デコレータのドキュメントなどです。 dir関数を使用して、関数のすべてのメタ情報を一覧表示できます。 dir(sleep)、出力は次のとおりです。

['__ annotations__','__call__','__class__','__closure__','__code__','__defaults__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__get__','__getattribute__','__globals__','__gt__','__hash__','__init__','__kwdefaults__','__le__','__lt__','__module__','__name__','__ne__','__new__','__qualname__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__']

メタ情報がたくさんあることがわかります。使用する2つの属性は、 __name__ __doc__です。

また、 __doc__属性は関数のドキュメント情報であり、ヘルプ関数を介して表示できます。

装飾された関数のメタ情報を保存する理由###

デコレータ1のアプリケーションを書き直します。タイミングプロセスのスリープ機能は次のとおりです。

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)sleep(3)print(sleep.__name__)print(sleep.__doc__)

上記のコードの出力は次のとおりです。

3.0032713413238525
wrap
None

スリープ関数の __name__はスリープではなくラップであり、 __doc__属性はスリープ関数のdocstringではなく空であることがわかります。つまり、デコレータによって装飾された関数のメタ情報が変更されたため、この時点で、プログラムが関数のメタ情報を必要とする場合、問題が発生します。

装飾された関数のメタ情報を保存する方法###

解決策1:装飾された関数のメタ情報に手動で値を割り当てる####

例として __name__ __doc__の2つの属性を取り上げます

import time

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 wrap.__doc__ = fn.__doc__	#手動割り当て__doc__情報
 wrap.__name__ = fn.__name__	#手動割り当て__name__情報
 return wrap

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)if __name__ =="__main__":sleep(3)
 # print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)

出力は次のとおりです

3.004547119140625
sleep
This function is sleep.

__name__ __doc__の2つの属性が実際に正常に割り当てられていることがわかります。

メタ情報の割り当てのプロセスを関数として次のように書き直すことができます。

import time

def copy_properties(src, dst):	#メタ情報の割り当てプロセスを機能コピーに変更する_properties
 dst.__name__ = src.__name__
 dst.__doc__ = src.__doc__

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 copy_properties(fn, wrap)	#コピーを呼び出す_プロパティはメタ情報を変更する機能
 return wrap

@ timeit
def sleep(x):'''This function is sleep.'''
 time.sleep(x)if __name__ =="__main__":sleep(3)
 # print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)

この変更後、問題も解決できます。

copy_propertiesが関数を返すことができるように、copy_properties関数を引き続き変更します。

def copy_properties(src):
 def _copy(dst):	#ビルトインワン_コピー機能は簡単に返せます
  dst.__name__ = src.__name__
  dst.__doc__ = src.__doc__
 return _copy

def timeit(fn):
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 copy_properties(fn)(wrap)	#コピーを呼び出す_プロパティ関数
 return wrap

同じことが問題になる可能性があります。

_copy関数がデコレータになるようにcopy_properties関数を変更し続ける場合は、dstを渡してdstを返し、次のように変更します。

def copy_properties(src):	#最初にdstを修正し、srcを渡します
 def _copy(dst):	#着信dst
  dst.__name__ = src.__name__
  dst.__doc__ = src.__doc__
  return dst	#dstを返す
 return _copy	#デコレータを返します

def timeit(fn):
 @ copy_properties(fn)	#パラメータデコレータの使用方法
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 return wrap

copy_propertiesは、ここにパラメーターを持つデコレーターを返すため、デコレーターの使用方法に従ってラップ関数を直接装飾できます。copy_properties関数を変更するこのプロセスは、関数カレーと呼ばれます。

オプション2:functoolsライブラリの@wrapsデコレータを使用する####

functoolsライブラリの@wrapsデコレータは、本質的にcopy_properties関数の高度なバージョンであり、より多くの関数メタ情報が含まれています。まず、ラップデコレータのヘルプ情報を確認します。

import functools
help(functools.wraps)

ラップデコレータ関数のプロトタイプは次のとおりです。

wraps(wrapped, assigned=('module','name','qualname','doc','annotations'), updated=('dict',))

したがって、このデコレータはモジュールなどのメタ情報をコピーしますが、すべてのメタ情報をコピーするわけではなく、dictを更新します。

使用例は次のとおりです。

import time
import functools

def timeit(fn):
 @ functools.wraps(fn)	#ラップデコレータの使用
 def wrap(*args,**kwargs):
  start = time.time()
  ret =fn(*args,**kwargs)
  end = time.time()print(end - start)return ret
 return wrap

def sleep(x):
 time.sleep(x)print(sleep.__name__)print(sleep.__doc__)

パラメータを使用してデコレータを作成します##

上記のtimeitデコレータの場合、実行時間が数秒(1秒など)を超える関数の名前と実行時間を出力する必要がある場合は、入力時間間隔を表すパラメータsをデコレータに渡す必要があります。デフォルトは1です。 。

記述されたデコレータの外側で関数timeitSをラップすることができます。時間間隔sは、この関数のパラメータとして渡され、内部関数に表示されます。その後、この関数は記述されたデコレータを返します。

import time
import functools

def timeitS(s):
 def timeit(fn):
  @ functools.wraps(fn)
  def wrap(*args,**kwargs):
   start = time.time()
   ret =fn(*args,**kwargs)
   end = time.time()if end - start > s:print('call {} takes {}s'.format(fn.__name__, end - start))else:print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))return ret
  return wrap
 return timeit

@ timeitS(2)
def sleep(x):
 time.sleep(x)sleep(3)sleep(1)

出力は次のとおりです。

call sleep takes 3.001342535018921s
call sleep takes 1.000471830368042s less than 2

したがって、次のようなパラメータを持つデコレータを理解できます。

Recommended Posts

Pythonデコレータ
Pythonマルチスレッド
Python CookBook
Python FAQ
Python3辞書
python(you-get)
Python文字列
Pythonの基本
Python記述子
Pythonの基本2
Python exec
Pythonノート
Python3タプル
CentOS + Python3.6 +
Python Advanced(1)
Python IO
Pythonマルチスレッド
Pythonツールチェーン
Python3リスト
Pythonマルチタスク-日常
Pythonの概要
pythonの紹介
Pythonアナリティック
Pythonの基本
07.Python3関数
Pythonの基本3
Pythonマルチタスクスレッド
Pythonデコレータの簡単な使用例の概要
Python関数
python sys.stdout
python演算子
Pythonエントリ-3
Centos 7.5 python3.6
Python文字列
pythonキューキュー
Pythonの基本4
Pythonの基本5
Pythonクラスデコレータ、小さなデモを使用
Centos6はPython2.7.13をインストールします
Pythonは質問に答えます
Pythonの基本構文(1)
Pythonはloopメソッドを終了します
Ubuntu16アップグレードPython3
Centos7はPython3.6をインストールします。
ubuntu18.04インストールpython2
Pythonの古典的なアルゴリズム
ubuntuを再学習します--python3
Python2.7 [インストールチュートリアル]
Python文字列操作
Python 3.9が登場!
Python研究ノート(1)
CentOS7アップグレードpython3
Python3の基本構文
Pythonレビュー1
linux + ubuntuはpythonを解決します
pythonの関数
Python学習変数タイプ
CentOSはPython3.6をインストールします
Pythonファイル操作
ubuntu12.04インストールpython3
Pythonのデザインパターン