Python Language
Dekorateure
Suche…
Einführung
Decorator-Funktionen sind Software-Designmuster. Sie ändern die Funktionalität einer Funktion, Methode oder Klasse dynamisch, ohne dass Sie Unterklassen direkt verwenden oder den Quellcode der dekorierten Funktion ändern müssen. Bei richtiger Anwendung können Dekorateure zu mächtigen Werkzeugen im Entwicklungsprozess werden. Dieses Thema behandelt die Implementierung und Anwendung von Decorator-Funktionen in Python.
Syntax
def decorator_function (f): pass # definiert einen Dekorateur mit dem Namen decorator_function
@decorator_function
def decor_function (): pass # Die Funktion ist jetzt mit decorator_function umschlossen (dekoriert mit)@decorator_function
= decorator_function (@decorator_function
) # Dies entspricht der Verwendung der syntaktischen Zucker@decorator_function
Parameter
Parameter | Einzelheiten |
---|---|
f | Die zu dekorierende Funktion (verpackt) |
Dekorateurfunktion
Dekorateure verbessern das Verhalten anderer Funktionen oder Methoden. Jede Funktion, die eine Funktion als Parameter übernimmt und eine erweiterte Funktion zurückgibt, kann als Dekorator verwendet werden .
# This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
return f
@super_secret_function
def my_function():
print("This is my secret function.")
Die @
-Notation ist ein syntaktischer Zucker, der dem folgenden entspricht:
my_function = super_secret_function(my_function)
Es ist wichtig, dies zu beachten, um zu verstehen, wie die Dekorateure arbeiten. Diese "ungezugte" Syntax macht deutlich, warum die Decorator-Funktion eine Funktion als Argument verwendet und warum sie eine andere Funktion zurückgeben soll. Es zeigt auch, was passiert, wenn Sie keine Funktion zurückgeben:
def disabled(f):
"""
This function returns nothing, and hence removes the decorated function
from the local scope.
"""
pass
@disabled
def my_function():
print("This function can no longer be called...")
my_function()
# TypeError: 'NoneType' object is not callable
Daher definieren wir normalerweise eine neue Funktion im Dekorateur und geben sie zurück. Diese neue Funktion würde zuerst etwas tun, was sie tun muss, dann die ursprüngliche Funktion aufrufen und schließlich den Rückgabewert verarbeiten. Betrachten Sie diese einfache Dekorationsfunktion, die die Argumente druckt, die die ursprüngliche Funktion empfängt, und sie dann aufruft.
#This is the decorator
def print_args(func):
def inner_func(*args, **kwargs):
print(args)
print(kwargs)
return func(*args, **kwargs) #Call the original function with its arguments.
return inner_func
@print_args
def multiply(num_a, num_b):
return num_a * num_b
print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function.
Dekorateur Klasse
Wie in der Einleitung erwähnt, ist ein Dekorateur eine Funktion, die auf eine andere Funktion angewendet werden kann, um ihr Verhalten zu verbessern. Der syntaktische Zucker entspricht dem Folgenden: my_func = decorator(my_func)
. Aber was wäre, wenn der decorator
stattdessen eine Klasse wäre? Die Syntax würde weiterhin funktionieren, mit der Ausnahme, dass my_func
jetzt durch eine Instanz der decorator
Klasse ersetzt wird. Wenn diese Klasse die magische Methode __call__()
implementiert, ist es weiterhin möglich, my_func
als Funktion zu verwenden:
class Decorator(object):
"""Simple decorator class."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Before the function call.')
res = self.func(*args, **kwargs)
print('After the function call.')
return res
@Decorator
def testfunc():
print('Inside the function.')
testfunc()
# Before the function call.
# Inside the function.
# After the function call.
Beachten Sie, dass eine mit einem Klassendekorateur dekorierte Funktion aus Sicht der Typenprüfung nicht mehr als "Funktion" betrachtet wird:
import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>
Dekorationsmethoden
Für Dekorationsmethoden müssen Sie eine zusätzliche __get__
Methode definieren:
from types import MethodType
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Inside the decorator.')
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
# Return a Method if it is called on an instance
return self if instance is None else MethodType(self, instance)
class Test(object):
@Decorator
def __init__(self):
pass
a = Test()
Im Dekorateur.
Warnung!
Klassendekorateure erzeugen nur eine Instanz für eine bestimmte Funktion, so dass das Dekorieren einer Methode mit einem Klassendekorateur denselben Dekorator für alle Instanzen dieser Klasse verwendet:
from types import MethodType
class CountCallsDecorator(object):
def __init__(self, func):
self.func = func
self.ncalls = 0 # Number of calls of this method
def __call__(self, *args, **kwargs):
self.ncalls += 1 # Increment the calls counter
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
return self if instance is None else MethodType(self, instance)
class Test(object):
def __init__(self):
pass
@CountCallsDecorator
def do_something(self):
return 'something was done'
a = Test()
a.do_something()
a.do_something.ncalls # 1
b = Test()
b.do_something()
b.do_something.ncalls # 2
Einen Dekorateur wie eine dekorierte Funktion aussehen lassen
Dekorateure entfernen normalerweise Funktionsmetadaten, da sie nicht gleich sind. Dies kann zu Problemen führen, wenn Meta-Programme für den dynamischen Zugriff auf Funktionsmetadaten verwendet werden. Metadaten enthalten auch die Dokumentzeichenfolgen und den Namen der Funktion. functools.wraps
lässt die dekorierte Funktion wie die ursprüngliche Funktion aussehen, indem sie mehrere Attribute in die Wrapper-Funktion kopiert.
from functools import wraps
Die beiden Methoden zum Umwickeln eines Dekorateurs erreichen beim Verbergen der ursprünglichen Funktion dasselbe. Es gibt keinen Grund, die Funktionsversion der Klassenversion vorzuziehen, es sei denn, Sie verwenden bereits eine Version über der anderen.
Als eine Funktion
def decorator(func):
# Copies the docstring, name, annotations and module to the decorator
@wraps(func)
def wrapped_func(*args, **kwargs):
return func(*args, **kwargs)
return wrapped_func
@decorator
def test():
pass
test.__name__
'Prüfung'
Als eine Klasse
class Decorator(object):
def __init__(self, func):
# Copies name, module, annotations and docstring to the instance.
self._wrapped = wraps(func)(self)
def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)
@Decorator
def test():
"""Docstring of test."""
pass
test.__doc__
"Docstring des Tests."
Dekorateur mit Argumenten (Dekorateurfabrik)
Ein Dekorateur hat nur ein Argument: die zu dekorierende Funktion. Es gibt keine Möglichkeit, andere Argumente zu übergeben.
Zusätzliche Argumente sind jedoch oft erwünscht. Der Trick besteht darin, eine Funktion zu erstellen, die willkürliche Argumente verwendet und einen Dekorator zurückgibt.
Dekorateurfunktionen
def decoratorfactory(message):
def decorator(func):
def wrapped_func(*args, **kwargs):
print('The decorator wants to tell you: {}'.format(message))
return func(*args, **kwargs)
return wrapped_func
return decorator
@decoratorfactory('Hello World')
def test():
pass
test()
Der Dekorateur möchte Ihnen sagen: Hallo Welt
Wichtige Notiz:
Bei solchen Dekorateurfabriken müssen Sie den Dekorateur mit einem Paar Klammern anrufen:
@decoratorfactory # Without parentheses
def test():
pass
test()
TypeError: decorator () fehlt 1 erforderliches Positionsargument: 'func'
Dekorateur Klassen
def decoratorfactory(*decorator_args, **decorator_kwargs):
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Inside the decorator with arguments {}'.format(decorator_args))
return self.func(*args, **kwargs)
return Decorator
@decoratorfactory(10)
def test():
pass
test()
Im Dekorateur mit Argumenten (10,)
Erstellen Sie eine Einzelklasse mit einem Dekorateur
Ein Singleton ist ein Muster, das die Instantiierung einer Klasse auf eine Instanz / ein Objekt beschränkt. Mit einem Dekorator können wir eine Klasse als Singleton definieren, indem die Klasse gezwungen wird, entweder eine vorhandene Instanz der Klasse zurückzugeben oder eine neue Instanz zu erstellen (falls diese nicht existiert).
def singleton(cls):
instance = [None]
def wrapper(*args, **kwargs):
if instance[0] is None:
instance[0] = cls(*args, **kwargs)
return instance[0]
return wrapper
Dieser Dekorator kann zu jeder Klassendeklaration hinzugefügt werden und stellt sicher, dass höchstens eine Instanz der Klasse erstellt wird. Alle nachfolgenden Aufrufe geben die bereits vorhandene Klasseninstanz zurück.
@singleton
class SomeSingletonClass:
x = 2
def __init__(self):
print("Created!")
instance = SomeSingletonClass() # prints: Created!
instance = SomeSingletonClass() # doesn't print anything
print(instance.x) # 2
instance.x = 3
print(SomeSingletonClass().x) # 3
Es ist also egal, ob Sie über Ihre lokale Variable auf die Klasseninstanz verweisen oder eine andere "Instanz" erstellen, Sie erhalten immer dasselbe Objekt.
Verwenden eines Dekorators, um eine Funktion festzulegen
import time
def timer(func):
def inner(*args, **kwargs):
t1 = time.time()
f = func(*args, **kwargs)
t2 = time.time()
print 'Runtime took {0} seconds'.format(t2-t1)
return f
return inner
@timer
def example_function():
#do stuff
example_function()