Detaillierte Erklärung der Python-Dekoratoren

高洛峰
Freigeben: 2016-11-01 11:24:36
Original
1251 Leute haben es durchsucht

Dekoratoren in Python sind eine Hürde für den Einstieg in Python. Sie sind da, egal ob Sie sie überwinden oder nicht.

Warum braucht man Dekoratoren?

Wir gehen davon aus, dass Ihr Programm die beiden Funktionen say_hello() und say_goodbye() implementiert.

def say_hello(): 
    print "hello!" 
     
def say_goodbye(): 
    print "hello!"  # bug here 
 
if __name__ == '__main__': 
    say_hello() 
    say_goodbye()
Nach dem Login kopieren

Aber beim eigentlichen Aufruf stellten wir fest, dass das Programm einen Fehler gemacht hat und der obige Code zwei Hallos ausgegeben hat. Nach dem Debuggen stellen Sie fest, dass say_goodbye() falsch ist. Der Chef verlangt, dass der Name der Eingabefunktion aufgezeichnet wird, bevor jede Methode aufgerufen wird, zum Beispiel:

[DEBUG]: Enter say_hello() 
Hello! 
[DEBUG]: Enter say_goodbye() 
Goodbye!
Nach dem Login kopieren

Okay, der kleine A ist Absolvent und er hat es so implementiert.

def say_hello(): 
    print "[DEBUG]: enter say_hello()" 
    print "hello!" 
 
def say_goodbye(): 
    print "[DEBUG]: enter say_goodbye()" 
    print "hello!" 
 
if __name__ == '__main__': 
    say_hello() 
    say_goodbye()
Nach dem Login kopieren

Sehr niedrig, oder? Klein B arbeitet schon seit einiger Zeit und hat Klein A gesagt, dass er so schreiben könnte.

def debug(): 
    import inspect 
    caller_name = inspect.stack()[1][3] 
    print "[DEBUG]: enter {}()".format(caller_name)    
 
def say_hello(): 
    debug() 
    print "hello!" 
 
def say_goodbye(): 
    debug() 
    print "goodbye!" 
 
if __name__ == '__main__': 
    say_hello() 
    say_goodbye()
Nach dem Login kopieren

Ist es natürlich besser, aber es ist unangenehm, die Funktion debug() in jeder Geschäftsfunktion aufzurufen? Was ist, wenn der Chef sagt, dass für say-bezogene Funktionen kein Debuggen erforderlich ist? -bezogene Funktionen? Ist das notwendig?

Dann sollte der Dekorateur zu diesem Zeitpunkt erscheinen.

Ein Dekorator ist im Wesentlichen eine Python-Funktion, die es anderen Funktionen ermöglicht, zusätzliche Funktionen hinzuzufügen, ohne Codeänderungen vorzunehmen. Der Rückgabewert des Dekorators ist ebenfalls ein Funktionsobjekt. Es wird häufig in Szenarien mit übergreifenden Anforderungen verwendet, z. B. Protokolleinfügung, Leistungstests, Transaktionsverarbeitung, Caching, Berechtigungsüberprüfung usw. Dekoratoren sind ein hervorragendes Design zur Lösung dieser Art von Problemen. Mit Dekoratoren können wir eine große Menge ähnlichen Codes extrahieren, der nichts mit der Funktion selbst zu tun hat, und ihn weiterhin wiederverwenden.

Kurz gesagt besteht die Rolle eines Dekorateurs darin, einer vorhandenen Funktion oder einem vorhandenen Objekt zusätzliche Funktionalität hinzuzufügen.

So schreibt man einen Dekorator

In den frühen Tagen (Python-Version < 2.4, vor 2004) war die Art und Weise, einer Funktion zusätzliche Funktionalität hinzuzufügen, so. < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

def debug(func): 
    def wrapper(): 
        print "[DEBUG]: enter {}()".format(func.__name__) 
        return func() 
    return wrapper 
 
def say_hello(): 
    print "hello!" 
 
say_hello = debug(say_hello)  # 添加功能并保持原函数名不变
Nach dem Login kopieren

Die obige Debug-Funktion ist eigentlich ein Dekorator. Sie umschließt die ursprüngliche Funktion und gibt eine andere Funktion zurück, wodurch einige zusätzliche Funktionen hinzugefügt werden. Da das Schreiben auf diese Weise nicht sehr elegant ist, unterstützen spätere Versionen von Python @ syntaktischen Zucker. Der folgende Code entspricht der früheren Schreibweise.

def debug(func): 
    def wrapper(): 
        print "[DEBUG]: enter {}()".format(func.__name__) 
        return func() 
    return wrapper 
 
@debug 
def say_hello(): 
    print "hello!"
Nach dem Login kopieren

Dies ist der einfachste Dekorator, aber es gibt ein Problem, wenn die dekorierte Funktion Parameter übergeben muss, dann wird dieser Dekorator kaputt gehen. Da die zurückgegebene Funktion keine Parameter akzeptieren kann, können Sie den Wrapper der Dekoratorfunktion so angeben, dass er dieselben Parameter wie die ursprüngliche Funktion akzeptiert, z. B.:

def debug(func): 
    def wrapper(something):  # 指定一毛一样的参数 
        print "[DEBUG]: enter {}()".format(func.__name__) 
        return func(something) 
    return wrapper  # 返回包装过函数 
 
@debug 
def say(something): 
    print "hello {}!".format(something)
Nach dem Login kopieren

Auf diese Weise lösen Sie ein Problem, fügen aber N weitere hinzu Probleme. Da es Tausende von Funktionen gibt, kümmern Sie sich nur um Ihre eigene Funktion. Wer weiß, wie die Funktionsparameter anderer Leute aussehen? Glücklicherweise bietet Python variable Parameter *args und Schlüsselwortparameter **kwargs jede Zielfunktion

def debug(func): 
    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数 
        print "[DEBUG]: enter {}()".format(func.__name__) 
        print &#39;Prepare and say...&#39;, 
        return func(*args, **kwargs) 
    return wrapper  # 返回 
 
@debug 
def say(something): 
    print "hello {}!".format(something)
Nach dem Login kopieren

An diesem Punkt beherrschen Sie die grundlegende Methode zum Schreiben von Dekoratoren vollständig.

Erweiterte Dekoratoren

Dekoratoren mit Parametern und Klassendekoratoren sind fortgeschrittene Inhalte. Bevor Sie diese Dekoratoren verstehen, ist es am besten, ein gewisses Verständnis für Funktionsabschlüsse und Dekoratorschnittstellenkonventionen zu haben. (Siehe http://betacat.online/posts/p...

Decorator mit Parametern

Angenommen, unser vorheriger Decorator muss mehr als nur die Eingabe eines bestimmten Protokolls nach einer Funktion ausführen Informationen werden gedruckt und die Protokollebene muss angegeben werden:

def logging(level): 
    def wrapper(func): 
        def inner_wrapper(*args, **kwargs): 
            print "[{level}]: enter function {func}()".format( 
                level=level, 
                func=func.__name__) 
            return func(*args, **kwargs) 
        return inner_wrapper 
    return wrapper 
 
@logging(level=&#39;INFO&#39;) 
def say(something): 
    print "say {}!".format(something) 
 
# 如果没有使用@语法,等同于 
# say = logging(level=&#39;INFO&#39;)(say) 
 
@logging(level=&#39;DEBUG&#39;) 
def do(something): 
    print "do {}...".format(something) 
 
if __name__ == &#39;__main__&#39;: 
    say(&#39;hello&#39;) 
    do("my work")
Nach dem Login kopieren

Ist es ein wenig verwirrend, wenn ein Dekorator mit Parametern versehen ist? Wird es auf eine Funktion wie @logging(level='DEBUG') angewendet, ist es tatsächlich eine Funktion und wird sofort ausgeführt. Solange das zurückgegebene Ergebnis ein Dekorator ist, gibt es kein Problem .

Klassenbasierter Dekorator

Die Dekoratorfunktion ist eigentlich eine solche Schnittstellenbeschränkung, dass sie ein aufrufbares Objekt als Parameter akzeptieren und dann ein aufrufbares Objekt in Python zurückgeben muss Funktionen, aber es gibt Ausnahmen. Solange ein Objekt die Methode __call__() überlädt, ist das Objekt aufrufbar, z. B. mit Unterstrichen davor und danach. Methoden werden in Python als integrierte Methoden bezeichnet und manchmal auch als magische Methoden Das Überladen dieser magischen Methoden ändert im Allgemeinen das interne Verhalten des Objekts.

Um auf das Konzept der Dekoratoren zurückzukommen, erfordern Dekoratoren den Empfang eines aufrufbaren Objekts und die Rückgabe eines aufrufbaren Objekts. nicht zu streng, siehe unten), daher können wir auch Klassen verwenden, um sie zu implementieren. Der Konstruktor __init__() akzeptiert eine Funktion, überlädt dann __call__() und gibt eine Funktion zurück, die auch den Effekt einer Dekoratorfunktion erzielen kann
class Test(): 
    def __call__(self): 
        print &#39;call me!&#39; 
 
t = Test() 
t()  # call me
Nach dem Login kopieren

Klassendekorator mit Parametern

Wenn Sie einen Dekorator mit Parametern über eine Klasse implementieren müssen, ist dies etwas komplizierter als im vorherigen Beispiel. Dann ist dies beim Konstruktor nicht der Fall Akzeptieren Sie eine Funktion, aber speichern Sie die über die Klasse Up übergebenen Parameter. Wenn Sie dann die __call__-Methode überladen, müssen Sie eine Funktion akzeptieren und eine Funktion zurückgeben

Prinzipien von integrierten Dekoratoren und gewöhnlichen Dekoratoren, aber sie geben keine Funktion, sondern ein Klassenobjekt zurück, daher ist es etwas schwieriger zu verstehen >Bevor Sie diesen Dekorator verstehen, müssen Sie wissen, wie man ihn ohne Dekoration verwendet
def getx(self): 
    return self._x 
 
def setx(self, value): 
    self._x = value 
     
def delx(self): 
   del self._x 
 
# create a property 
x = property(getx, setx, delx, "I am doc for x property")
Nach dem Login kopieren

以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。

@property 
def x(self): ... 
 
# 等同于 
 
def x(self): ... 
x = property(x)
Nach dem Login kopieren

属性有三个装饰器:setter, getter, deleter ,都是在property()的基础上做了一些封装,因为setter和deleter是property()的第二和第三个参数,不能直接套用@语法。getter装饰器和不带getter的属性装饰器效果是一样的,估计只是为了凑数,本身没有任何存在的意义。经过@property装饰过的函数返回的不再是一个函数,而是一个property对象。

>>> property() 
<property object at 0x10ff07940>
Nach dem Login kopieren

@staticmethod,@classmethod

有了@property装饰器的了解,这两个装饰器的原理是差不多的。@staticmethod返回的是一个staticmethod类对象,而@classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。

class classmethod(object): 
    """ 
    classmethod(function) -> method 
    """     
    def __init__(self, function): # for @classmethod decorator 
        pass 
    # ... 
class staticmethod(object): 
    """ 
    staticmethod(function) -> method 
    """ 
    def __init__(self, function): # for @staticmethod decorator 
        pass 
    # ...
Nach dem Login kopieren

装饰器的@语法就等同调用了这两个类的构造函数。

class Foo(object): 
 
    @staticmethod 
    def bar(): 
        pass 
     
    # 等同于 bar = staticmethod(bar)
Nach dem Login kopieren

至此,我们上文提到的装饰器接口定义可以更加明确一些,装饰器必须接受一个callable对象,其实它并不关心你返回什么,可以是另外一个callable对象(大部分情况),也可以是其他类对象,比如property。

装饰器里的那些坑

装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。

位置错误的代码

让我们直接看示例代码。

def html_tags(tag_name): 
    print &#39;begin outer function.&#39; 
    def wrapper_(func): 
        print "begin of inner wrapper function." 
        def wrapper(*args, **kwargs): 
            content = func(*args, **kwargs) 
            print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content) 
        print &#39;end of inner wrapper function.&#39; 
        return wrapper 
    print &#39;end of outer function&#39; 
    return wrapper_ 
 
@html_tags(&#39;b&#39;) 
def hello(name=&#39;Toby&#39;): 
    return &#39;Hello {}!&#39;.format(name) 
 
hello() 
hello()
Nach dem Login kopieren

在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:

begin outer function. 
end of outer function 
begin of inner wrapper function. 
end of inner wrapper function. 
<b>Hello Toby!</b> 
<b>Hello Toby!</b>
Nach dem Login kopieren

错误的函数签名和文档

装饰器装饰过的函数看上去名字没变,其实已经变了。

def logging(func): 
    def wrapper(*args, **kwargs): 
        """print log before a function.""" 
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) 
        return func(*args, **kwargs) 
    return wrapper 
 
@logging 
def say(something): 
    """say something""" 
    print "say {}!".format(something) 
 
print say.__name__  # wrapper
Nach dem Login kopieren

为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。

say = logging(say)
Nach dem Login kopieren

logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。

使用标准库里的functools.wraps,可以基本解决这个问题。

from functools import wraps 
 
def logging(func): 
    @wraps(func) 
    def wrapper(*args, **kwargs): 
        """print log before a function.""" 
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) 
        return func(*args, **kwargs) 
    return wrapper 
 
@logging 
def say(something): 
    """say something""" 
    print "say {}!".format(something) 
 
print say.__name__  # say 
print say.__doc__ # say something
Nach dem Login kopieren

看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。

import inspect 
print inspect.getargspec(say)  # failed 
print inspect.getsource(say)  # failed
Nach dem Login kopieren

如果要彻底解决这个问题可以借用第三方包,比如wrapt。后文有介绍。

不能装饰@staticmethod 或者 @classmethod

当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。

class Car(object): 
    def __init__(self, model): 
        self.model = model 
 
    @logging  # 装饰实例方法,OK 
    def run(self): 
        print "{} is running!".format(self.model) 
 
    @logging  # 装饰静态方法,Failed 
    @staticmethod 
    def check_model_for(obj): 
        if isinstance(obj, Car): 
            print "The model of your car is {}".format(obj.model) 
        else: 
            print "{} is not a car!".format(obj) 
 
""" 
Traceback (most recent call last): 
... 
  File "example_4.py", line 10, in logging 
    @wraps(func) 
  File "C:\Python27\lib\functools.py", line 33, in update_wrapper 
    setattr(wrapper, attr, getattr(wrapped, attr)) 
AttributeError: &#39;staticmethod&#39; object has no attribute &#39;__module__&#39; 
"""
Nach dem Login kopieren

前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。

class Car(object): 
    def __init__(self, model): 
        self.model = model 
 
    @staticmethod 
    @logging  # 在@staticmethod之前装饰,OK 
    def check_model_for(obj): 
        pass
Nach dem Login kopieren

如何优化你的装饰器

嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。

decorator.py

decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。

from decorator import decorate 
 
def wrapper(func, *args, **kwargs): 
    """print log before a function.""" 
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) 
    return func(*args, **kwargs) 
 
def logging(func): 
    return decorate(func, wrapper)  # 用wrapper装饰func
Nach dem Login kopieren

你也可以使用它自带的@decorator装饰器来完成你的装饰器。

from decorator import decorator 
 
@decorator 
def logging(func, *args, **kwargs): 
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) 
    return func(*args, **kwargs)
Nach dem Login kopieren

decorator.py实现的装饰器能完整保留原函数的name,doc和args,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)。

wrapt

wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。

import wrapt 
 
# without argument in decorator 
@wrapt.decorator 
def logging(wrapped, instance, args, kwargs):  # instance is must 
    print "[DEBUG]: enter {}()".format(wrapped.__name__) 
    return wrapped(*args, **kwargs) 
 
@logging 
def say(something): pass
Nach dem Login kopieren

使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

如果你需要使用wrapt写一个带参数的装饰器,可以这样写。

def logging(level): 
    @wrapt.decorator 
    def wrapper(wrapped, instance, args, kwargs): 
        print "[{}]: enter {}()".format(level, wrapped.__name__) 
        return wrapped(*args, **kwargs) 
    return wrapper 
 
@logging(level="INFO") 
def do(work): pass
Nach dem Login kopieren

关于wrapt的使用,建议查阅官方文档,在此不在赘述。

http://wrapt.readthedocs.io/e...

小结

Pythons Dekoratoren sind nicht dasselbe wie Javas Annotationen, noch sind sie dasselbe wie Attribute in C#. Es handelt sich um zwei völlig unterschiedliche Konzepte.

Das Konzept des Dekorators besteht darin, die ursprüngliche Funktion und das ursprüngliche Objekt zu stärken, was einer erneuten Kapselung entspricht. Daher heißt die Dekoratorfunktion im Allgemeinen Wrapper (), was Verpackung bedeutet. Eine Funktion führt ihre Funktion erst aus, wenn sie aufgerufen wird. Beispielsweise kann der Dekorator @logging zusätzliche Protokolle ausgeben, wenn die Funktion ausgeführt wird, und die mit @cache dekorierte Funktion kann Berechnungsergebnisse usw. zwischenspeichern.

Anmerkungen und Features fügen der Zielfunktion oder dem Zielobjekt einige Attribute hinzu, was einer Klassifizierung gleichkommt. Diese Eigenschaften können durch Reflexion ermittelt werden, und während der Ausführung des Programms können verschiedene charakteristische Funktionen oder Objekte eingegriffen werden. Beispielsweise werden Funktionen mit Setup als Vorbereitungsschritte ausgeführt oder alle Funktionen mit TestMethod werden nacheinander gefunden und ausgeführt usw.

Bisher habe ich die Rede über die mir bekannten Dekorateure beendet, aber es gibt noch einige Dinge, die noch nicht erwähnt wurden, wie zum Beispiel Dekorateure für dekorative Klassen. Werde bei Gelegenheit noch mehr hinzufügen. Danke fürs Zuschauen.


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage