Maison > Article > développement back-end > Introduction détaillée à l'utilisation des décorateurs Python (exemples de code)
Ce que cet article vous apporte est une introduction détaillée à l'utilisation (exemple de code) des décorateurs Python. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
En Python, les décorateurs sont généralement utilisés pour décorer des fonctions afin d'implémenter des fonctions publiques et réaliser la réutilisation du code. Ajoutez @xxxx avant la définition de la fonction, puis la fonction injectera certains comportements, ce qui est incroyable ! Cependant, ce n’est que du sucre syntaxique.
Scénario
Supposons que certaines fonctions de travail soient utilisées pour traiter les données de différentes manières :
def work_bar(data): pass def work_foo(data): pass
Nous voulons le faire avant d'appeler le fonction Que dois-je faire si le journal est affiché après /?
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
Et s'il y a plusieurs appels de code ? Rien que d'y penser, ça me fait peur !
La solution idiote n'est rien de plus qu'une trop grande redondance de code, et vous devez la réécrire pour chaque appel de fonctionlogging
. Vous pouvez encapsuler cette partie de logique redondante dans une nouvelle fonction :
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
De cette façon, appelez smart_work_bar
à chaque fois :
smart_work_bar(1) # ... smart_work_bar(some_data)
Cela a l'air parfait... Cependant, lorsque work_foo
a également le même besoin, devons-nous le mettre en œuvre à nouveau smart_work_foo
? Ce n’est évidemment pas scientifique !
Ne vous inquiétez pas, nous pouvons utiliser des fermetures :
def log_call(func): def proxy(*args, **kwargs): logging.info('begin call: {name}'.format(name=func.func_name)) result = func(*args, **kwargs) logging.info('call done: {name}'.format(name=func.func_name)) return result return proxy
Cette fonction reçoit un objet fonction (fonction proxy) en paramètre et renvoie une fonction proxy. Lors de l'appel de la fonction proxy, le journal est d'abord affiché, puis la fonction proxy est appelée, le journal est affiché une fois l'appel terminé et enfin le résultat de l'appel est renvoyé. De cette façon, n’atteint-il pas l’objectif de généralisation ? ——Pour toute fonction proxy func
, log_call
peut être facilement gérée.
smart_work_bar = log_call(work_bar) smart_work_foo = log_call(work_foo) smart_work_bar(1) smart_work_foo(1) # ... smart_work_bar(some_data) smart_work_foo(some_data)
Dans la ligne 1
, log_call
reçoit le paramètre work_bar
, renvoie une fonction proxy proxy
et l'assigne à smart_work_bar
. Dans la ligne 4
, appelez smart_work_bar
, qui est la fonction proxy proxy
, affichez d'abord le journal, puis appelez func
, qui est work_bar
, et enfin affichez le journal. Notez que dans la fonction proxy, func
est étroitement lié à l'objet work_bar
transmis. Il s'agit de la fermeture .
Encore une fois, vous pouvez écraser le nom de la fonction proxy. Le préfixer avec smart_
pour obtenir un nouveau nom est encore un peu fastidieux :
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
. vient en premier Jetez un oeil au code suivant :
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
Bien que le code ne soit plus redondant, il n'est toujours pas assez intuitif. À ce stade, le sucre de syntaxe arrive ~~~
@log_call def work_bar(data): pass
Par conséquent, faites attention à une chose (c'est nous qui soulignons La fonction de @log_call
ici est simplement de dire le compilateur pour insérer du code Python
. work_bar = log_call(work_bar)
? eval_now
def eval_now(func): return func()Ça a l'air étrange. Il n'y a pas de fonction proxy définie. Est-il considéré comme un décorateur ?
@eval_now def foo(): return 1 print fooCe code génère
, qui consiste à appeler et à évaluer la fonction. Alors à quoi ça sert ? Tu ne peux pas simplement écrire 1
directement ? Dans cet exemple simple, il est bien entendu possible d’écrire ainsi. Regardons un exemple plus complexe : initialisez un objet de journal : foo = 1
# some other code before... # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) # again some other code after...Utilisez la méthode
: eval_now
# some other code before... @eval_now def logger(): # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger # again some other code after...Le but des deux morceaux de code est le même. Mais ce dernier est évidemment plus clair et a le style de blocs de code. Plus important encore, les appels de fonction sont initialisés dans l'espace de noms local pour éviter que les variables temporaires (telles que
, etc.) ne polluent les espaces de noms externes (tels que le global). formatter
def log_slow_call(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > 1: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxyLes lignes
et 3
sont respectivement dans les appels de fonction Echantillonnage de l'heure actuelle avant et après, la ligne 5
calcule la durée de l'appel et génère un journal d'avertissement si cela prend plus d'une seconde. 7
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # 没有日志输出 sleep_seconds(2) # 输出警告日志Cependant, le réglage du seuil dépend toujours de la situation et différentes fonctions peuvent définir des valeurs différentes. Ce serait bien s'il y avait un moyen de paramétrer le seuil :
def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxyCependant, le sucre syntaxique
appelle toujours le décorateur avec la fonction décorée comme paramètre, ce qui signifie qu'il n'y a aucune chance de passez le paramètre @xxxx
. Ce qu'il faut faire? ——Utilisez une fermeture pour encapsuler les paramètres threshold
: threshold
def log_slow_call(threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy return decorator @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)De cette façon,
appelle la fonction de retour log_slow_call(threshold=0.5)
, et la fonction a une variable de fermeture decorator
avec une valeur de threshold
. 0.5
Décorezdecorator
à nouveau. sleep_seconds
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)La Vierge peut être mécontente de la paire de parenthèses dans la première ligne, elle peut donc être améliorée comme ceci :
def log_slow_call(func=None, threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy if func is None: return decorator else: return decorator(func)Cette méthode d'écriture est compatible avec deux usages différents, l'usage
seuil par défaut (pas d'usage d'appel A
seuil personnalisé (avec appel). Dans l'utilisation B
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds) # Case B @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
, ce qui se passe est A
, c'est-à-dire que le paramètre log_slow_call(sleep_seconds)
n'est pas vide. Ceci est directement appelé func
pour envelopper et renvoyer (le seuil est. la valeur par défaut). decorator
用法B
中,先发生的是log_slow_call(threshold=0.5)
,func
参数为空,直接返回新的装饰器decorator
,关联闭包变量threshold
,值为0.5
;然后,decorator
再装饰函数sleep_seconds
,即decorator(sleep_seconds)
。注意到,此时threshold
关联的值是0.5
,完成定制化。
你可能注意到了,这里最好使用关键字参数这种调用方式——使用位置参数会很丑陋:
# Case B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
当然了,函数调用尽量使用关键字参数是一种极佳实践,含义清晰,在参数很多的情况下更是如此。
上节介绍的写法,嵌套层次较多,如果每个类似的装饰器都用这种方法实现,还是比较费劲的(脑子不够用),也比较容易出错。
假设有一个智能装饰器smart_decorator
,修饰装饰器log_slow_call
,便可获得同样的能力。这样,log_slow_call
定义将变得更清晰,实现起来也更省力啦:
@smart_decorator def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
脑洞开完,smart_decorator
如何实现呢?其实也简单:
def smart_decorator(decorator): def decorator_proxy(func=None, **kwargs): if func is not None: return decorator(func=func, **kwargs) def decorator_proxy(func): return decorator(func=func, **kwargs) return decorator_proxy return decorator_proxy
smart_decorator
实现了以后,设想就成立了!这时,log_slow_call
,就是decorator_proxy
(外层),关联的闭包变量decorator
是本节最开始定义的log_slow_call
(为了避免歧义,称为real_log_slow_call
)。log_slow_call
支持以下各种用法:
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,执行的是decorator_proxy(sleep_seconds)
(外层),func
非空,kwargs
为空;直接执行decorator(func=func, **kwargs)
,即real_log_slow_call(sleep_seconds)
,结果是关联默认参数的proxy
。
# Case B # Same to Case A @log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
用法B
中,先执行decorator_proxy()
,func
及kwargs
均为空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(func, **kwargs)
,等价于real_log_slow_call(sleep_seconds)
,效果与用法A
一致。
# Case C @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法C
中,先执行decorator_proxy(threshold=0.5)
,func
为空但kwargs
非空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(sleep_seconds, **kwargs)
,等价于real_log_slow_call(sleep_seconds, threshold=0.5)
,阈值实现自定义!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!