English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In Python werden Dekoratoren in der Regel verwendet, um Funktionen zu dekorieren, um gemeinsame Funktionen zu realisieren und den Zweck der Code-Wiederverwendung zu erreichen. Man fügt vor der Funktion @xxxx hinzu, und die Funktion wird bestimmte Verhaltensweisen injiziert, was sehr magisch ist! Aber das ist nur ein Syntax-Hack.
Szenario
Angenommen, es gibt einige Arbeitsfunktionen, die verschiedene Datenverarbeitung durchführen:
def work_bar(data): pass def work_foo(data): pass
Wir möchten vor der Funktionsauf呼叫:/Wie schreibe ich nach dem Output der Protokollierung?
Dämlicher Ansatz
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
Was, wenn es mehrere Codeaufrufe gibt? Man denkt sofort, dass es beängstigend sein könnte!
Funktionsschalen
Der dämliche Ansatz besteht darin, dass es zu viel Code-Redundanz gibt, und man muss bei jeder Funktion aufruf logging schreiben. Man kann diese Redundanzlogik in eine neue Funktion einbetten:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
Dadurch kann man jedes Mal smart_work_bar aufrufen:
smart_work_bar(1) # ... smart_work_bar(some_data)
Allgemeine geschlossene Blöcke
Es scheint ziemlich perfekt zu sein... Aber wenn work_foo auch die gleiche Bedürfnisse hat, müssen wir smart_work_foo erneut implementieren? Das ist offensichtlich nicht wissenschaftlich!
Keine Eile, wir können durch geschlossene Blöcke:
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
Diese Funktion nimmt ein Funktionsobjekt (durchgeführte Funktion) als Parameter und gibt einen Proxy zurück. Beim Aufruf des Proxy wird zuerst das Protokoll ausgegeben, dann die durchgeführte Funktion aufgerufen, und schließlich wird das Protokoll erneut ausgegeben, und am Ende wird das Aufrufergebnis zurückgegeben. So erreichen wir das Ziel der Generalisierung - für jede durchgeführte Funktion func kann log_call problemlos verwendet werden.
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)
第1In der Zeile akzeptiert log_call den Parameter work_bar, gibt einen Proxy zurück und weist ihn als smart_work_bar zu.4In der Zeile wird smart_work_bar, also der Proxy, zuerst das Protokoll ausgeben und dann func, also work_bar, aufrufen, und schließlich noch einmal das Protokoll ausgeben. Beachten Sie, dass func im Proxy eng mit dem übergebenen work_bar-Objekt verbunden ist, was eine geschlossene Umgebung ist.
Noch eine Sache, es ist etwas mühsam, den Namen des durchgeführten Funktionsnamens zu überschreiben, indem man mit 'smart_' beginnt:
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
Syntaktische Zucker
Schauen wir uns zuerst folgenden Code an:
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
Obwohl der Code nicht mehr überflüssig ist, ist es trotzdem nicht besonders intuitiv zu sehen. In diesem Fall kommen die syntaktischen砂糖 ins Spiel~~~
@log_call def work_bar(data): pass
Daher beachten Sie etwas (Punkt hervorheben), die Funktion von @log_call hier ist nur: Es wird dem Python-Compiler mitgeteilt, den Code work_bar = log_call(work_bar) einzufügen.
Wertungs-Dekorator
Versuchen Sie vorher zu erraten, welche Funktion der Dekorator eval_now hat?
def eval_now(func): return func()
Schaut komisch aus, oder? Es wurde keine Proxy-Funktion definiert, ist das ein Dekorator?
@eval_now def foo(): return 1 print foo
Dieses Code-Snippet gibt aus1das bedeutet, die Auswertung der Funktion aufzurufen. Was ist dann der Nutzen? Direkt schreiben foo = 1Geht das nicht? In diesem einfachen Beispiel ist das natürlich so, aber schauen wir uns einen komplexeren Fall an - die Initialisierung eines Log-Objekts:
# einige andere Codes vor... # 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) # noch einige andere Codes nach...
Auf die Weise von eval_now:
# einige andere Codes vor... @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 # noch einige andere Codes nach...
Das Ziel der beiden Code-Schnipsel ist gleich, aber der Letzte ist显然 klarer und hat den Stil eines Code-Blocks. Wichtiger noch, die Funktionsaufrufe werden in der lokalen Namensraumumgebung initialisiert, was das Verunreinigen des externen Namensraums durch temporäre Variablen (wie formatter) verhindert.
Dekorator mit Parameter
Definiert ein Dekorator, um langsame Funktionsaufrufe zu protokollieren:
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 proxy
第3、5Zeilen messen die aktuelle Zeit vor und nach dem Funktionsauf call,7Zeilen berechnen die Aufwandszeit der Funktionsauf calls, geben Sie eine Warnung aus, wenn die Dauer mehr als eine Sekunde beträgt.
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # Kein Protokollausgabe sleep_seconds(2) # Warnungsprotokoll ausgeben
Allerdings muss die Einstellung des Schwellwerts immer im Kontext entschieden werden, verschiedene Funktionen können verschiedene Werte einstellen. Wenn der Schwellwert parametrisiert werden könnte, wäre das besser:
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
Allerdings wird die SyntaxSugar @xxxx immer mit der zu dekorierenden Funktion als Parameter den Decorator aufgerufen, das bedeutet, es gibt keine Möglichkeit, den Parameter threshold zu übergeben. Was also tun? - Verwenden Sie eine geschlossene Variablen zu verpacken den Parameter 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)
So, log_slow_call(threshold=0.5) Aufruf zurückgeben Funktion decorator, die die geschlossene Variablenvariable threshold hat, der Wert ist 0.5。decorator再装饰sleep_seconds。
Mit dem Standardwert, kann die Funktionsauf call immer noch nicht weggelassen werden:
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
Wenn der Krebs auf die ersten Klammern in der ersten Zeile stößt, kann dies so verbessert werden:
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)
Diese Schreibweise ist kompatibel mit beiden verschiedenen Verwendungen, Verwendung A mit Standardwert (kein Aufruf);Verwendung B mit benutzerdefiniertem Wert (Aufruf).
# Fall 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)
In der Verwendung von A, das发生的的事情是log_slow_call(sleep_seconds),也就是func参数是非空的,这是直接调decorator进行包装并返回(阈值是默认的)。
In der Verwendung von B, das erste, was passiert, ist log_slow_call(threshold=0.5),func参数为空,直接返回新的装饰器decorator,关联闭包变量threshold,值为0.5; dann wird der Decorator die Funktion sleep_seconds dekorieren, d.h. decorator(sleep_seconds). Beachten Sie, dass der Wert, der mit threshold verbunden ist, 0 ist.5,完成定制化。
Vielleicht haben Sie bemerkt, dass es hier am besten ist, diese Art der Aufrufweise mit Schlüsselparametern zu verwenden - die Verwendung von Positionsparametern würde hässlich aussehen:
# Case B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
Natürlich ist es eine hervorragende Praxis, bei der Funktionenauf calls so weit wie möglich Schlüsselparameter zu verwenden, da dies die Bedeutung klarer macht, insbesondere wenn viele Parameter vorhanden sind.
Intelligenter Dekorator
Die im letzten Abschnitt vorgestellte Methode hat viele Ebenen von Nestungen, wenn jeder ähnliche Dekorator mit dieser Methode implementiert wird, ist es ziemlich mühsam (der Kopf ist nicht groß genug) und auch leicht zu Fehler zu verursachen.
Angenommen, es gibt einen intelligenten Dekorator smart_decorator, der den Dekorator log_slow_call modifiziert, kann die gleiche Fähigkeit erhalten. Auf diese Weise wird die Definition von log_slow_call klarer und einfacher zu realisieren:
@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
Nachdem der Kopf frei ist, wie kann smart_decorator implementiert werden? Tatsächlich ist es auch einfach:
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
Nachdem smart_decorator implementiert wurde, hat sich die Vorstellung bestätigt! In diesem Moment ist log_slow_call, der decorator_proxy(Außenlayer) ist, der mit den geschlossenen Variablen decorator verbunden ist, der am Anfang dieses Abschnitts definiert wurde log_slow_call(um Missverständnisse zu vermeiden, wird es real_log_slow_call genannt). log_slow_call unterstützt die folgenden verschiedenen Verwendungen:
# Fall A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
In der Verwendung von A wird decorator_proxy(sleep_seconds)(Außenlayer) ausgeführt, func ist nicht leer, kwargs ist leer; func direkt ausführen, 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),阈值实现自定义!
声明:本文内容来自网络,版权归原作者所有。内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#oldtoolbag.com(在发邮件时,请将#更换为@进行举报,并提供相关证据。一经查实,本站将立即删除涉嫌侵权内容。)