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()


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow