Heim > Artikel > Backend-Entwicklung > Um Python-Dekoratoren zu verstehen, reicht es aus, diesen Artikel zu lesen
Bevor ich über Python-Dekoratoren spreche, möchte ich zunächst ein Beispiel geben, das zwar etwas schmutzig ist, aber für das Thema Dekoratoren sehr relevant ist.
Die Hauptfunktion der Unterwäsche, die jeder hat, besteht darin, unsere Schande zu verdecken, aber im Winter kann sie uns nicht vor Wind und Kälte schützen, was sollen wir tun? Eine Möglichkeit bestand darin, die Unterwäsche dicker und länger zu machen. Auf diese Weise hat sie nicht nur die Funktion, Scham zu bedecken, sondern sorgt auch für Wärme. Nachdem wir die Unterwäsche in eine Hose verwandelt haben , , obwohl es immer noch eine schamverdeckende Funktion hat, ist es im Grunde keine echte Unterwäsche mehr. Also haben kluge Leute Hosen erfunden und die Hosen direkt über die Unterwäsche gezogen, ohne die Unterwäsche zu beeinträchtigen. Mit der Hose wird dem Baby nicht mehr kalt. Dekorateure sind wie die Hosen, von denen wir hier sprechen. Sie spenden unserem Körper Wärme, ohne die Funktion der Unterwäsche zu beeinträchtigen.
Bevor Sie über Dekoratoren sprechen, müssen Sie zunächst eines verstehen: Funktionen in Python unterscheiden sich von Java und C++. Funktionen in Python können als Parameter wie gewöhnliche Variablen übergeben werden, zum Beispiel:
def foo(): print("foo")def bar(func): func()bar(foo)
Offiziell zurück zu unserem Thema. Ein Dekorator ist im Wesentlichen eine Python-Funktion oder -Klasse, die es anderen Funktionen oder Klassen ermöglicht, zusätzliche Funktionalität hinzuzufügen, ohne Codeänderungen vorzunehmen. Der Rückgabewert des Dekorators ist ebenfalls ein Funktions-/Klassenobjekt. Es wird häufig in Szenarien mit übergreifenden Anforderungen verwendet, z. B. Protokolleinfügung, Leistungstests, Transaktionsverarbeitung, Caching, Berechtigungsüberprüfung usw. Dekoratoren eignen sich hervorragend zur Lösung solcher Probleme. Mit Dekoratoren können wir eine große Menge ähnlichen Codes, der nichts mit der Funktion selbst zu tun hat, in Dekoratoren extrahieren und ihn weiterhin wiederverwenden. Kurz gesagt besteht der Zweck eines Dekorateurs darin, einem vorhandenen Objekt zusätzliche Funktionalität hinzuzufügen.
Schauen wir uns zunächst ein einfaches Beispiel an, obwohl der eigentliche Code möglicherweise viel komplizierter ist:
def foo(): print('i am foo')
Jetzt gibt es eine neue Anforderung, in der wir hoffen, das Ausführungsprotokoll des aufzuzeichnen Funktion, also fügen Sie im Code den Protokollcode hinzu:
def foo(): print('i am foo') logging.info("foo is running")
Wenn die Funktionen bar() und bar2() ähnliche Anforderungen haben, was sollte getan werden? Eine andere Protokollierung in der Bar-Funktion schreiben? Dies führt zu viel ähnlichem Code. Um das wiederholte Schreiben von Code zu reduzieren, können wir dies tun und eine neue Funktion neu definieren: Nachdem das Protokoll verarbeitet wurde, wird der eigentliche Geschäftscode ausgeführt
def use_logging(func): logging.warn("%s is running" % func.__name__) func()def foo(): print('i am foo')use_logging(foo)
So gibt es logischerweise kein Problem, die Funktion ist implementiert, aber wenn wir sie aufrufen, rufen wir nicht mehr die eigentliche Geschäftslogik-Foo-Funktion auf, sondern ersetzen sie durch die Funktion use_logging, die jetzt die ursprüngliche Codestruktur zerstört Gibt es einen besseren Weg, anstatt jedes Mal die ursprüngliche Funktion foo als Parameter an die Funktion use_logging zu übergeben? Natürlich gibt es das, die Antwort lautet: Dekorateure.
Einfacher Dekorator
def use_logging(func): def wrapper(): logging.warn("%s is running" % func.__name__) return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo() return wrapperdef foo(): print('i am foo')foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapperfoo() # 执行foo()就相当于执行 wrapper()
use_logging ist ein Dekorator, eine gewöhnliche Funktion, die die Funktion func umschließt, die die eigentliche Geschäftslogik ausführt. Es sieht so aus, als wäre foo mit use_logging dekoriert Wie oben gibt use_logging auch eine Funktion zurück, und der Name dieser Funktion ist Wrapper. In diesem Beispiel wird das Ein- und Aussteigen einer Funktion als Querschnitt bezeichnet. Diese Programmiermethode wird aspektorientierte Programmierung genannt.
@-Syntaxzucker
Wenn Sie schon länger mit Python in Kontakt sind, müssen Sie mit dem @-Symbol vertraut sein. Ja, das @-Symbol ist ein syntaktischer Zucker für Dekoratoren in Funktionen Der Ort, an dem die Definition beginnt, sodass der letzte Schritt der Neuzuweisung weggelassen werden kann.
def use_logging(func): def wrapper(): logging.warn("%s is running" % func.__name__) return func() return wrapper@use_loggingdef foo(): print("i am foo")foo()
Wie oben gezeigt, können wir mit @ den Satz foo = use_logging(foo) weglassen und direkt foo() aufrufen, um das gewünschte Ergebnis zu erhalten. Haben Sie gesehen, dass die Funktion foo() in keiner Weise geändert werden muss? Fügen Sie einfach dort, wo sie definiert ist, einen Dekorator hinzu. Der Aufruf ist immer noch derselbe wie zuvor. Wenn wir andere ähnliche Funktionen haben, können wir mit dem Aufruf fortfahren Der Dekorateur kann die Funktion dekorieren, ohne die Funktion wiederholt ändern oder neue Pakete hinzufügen zu müssen. Dadurch verbessern wir die Wiederverwendbarkeit des Programms und erhöhen die Lesbarkeit des Programms.
Der Grund, warum Dekoratoren in Python so bequem zu verwenden sind, liegt darin, dass Python-Funktionen als Parameter an andere Funktionen wie gewöhnliche Objekte übergeben werden können, anderen Variablen zugewiesen werden können und als Rückgabewerte verwendet werden können innerhalb einer anderen Funktion definiert.
*args, **kwargs
Jemand fragt sich vielleicht, was passiert, wenn meine Geschäftslogikfunktion foo Parameter erfordert? Zum Beispiel:
def foo(name): print("i am %s" % name)
Wir können die Parameter angeben, wenn wir die Wrapper-Funktion definieren:
def wrapper(name): logging.warn("%s is running" % func.__name__) return func(name) return wrapper
这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:
def wrapper(*args): logging.warn("%s is running" % func.__name__) return func(*args) return wrapper
如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?比如:
def foo(name, age=None, height=None): print("I am %s, age %s, height %s" % (name, age, height))
这时,你就可以把 wrapper 函数指定关键字函数:
def wrapper(*args, **kwargs): # args是一个数组,kwargs一个字典 logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) return wrapper
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) elif level == "info": logging.info("%s is running" % func.__name__) return func(*args) return wrapper return decorator@use_logging(level="warn")def foo(name='foo'): print("i am %s" % name)foo()
上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。
@use_logging(level="warn")等价于@decorator
类装饰器
没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending')@Foodef bar(): print ('bar')bar()
functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
# 装饰器def logged(func): def with_logging(*args, **kwargs): print func.__name__ # 输出 'with_logging' print func.__doc__ # 输出 None return func(*args, **kwargs) return with_logging# 函数@loggeddef f(x): """does some math""" return x + x * xlogged(f)
不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
from functools import wrapsdef logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ # 输出 'f' print func.__doc__ # 输出 'does some math' return func(*args, **kwargs) return with_logging@loggeddef f(x): """does some math""" return x + x * x
装饰器顺序
一个函数还可以同时定义多个装饰器,比如:
@a @b @c def f (): pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于
f = a(b(c(f)))
zhijun liu首发于知乎专栏Python之禅,原文地址:https://zhuanlan.zhihu.com/p/24900548