Sök…


Introduktion

Dekorationsfunktioner är mönster för programvarudesign. De förändrar dynamisk funktionen för en funktion, metod eller klass utan att behöva direkt använda underklasser eller ändra källkoden för den dekorerade funktionen. När de används korrekt kan dekoratörer bli kraftfulla verktyg i utvecklingsprocessen. Detta ämne täcker implementering och tillämpningar av dekoratörsfunktioner i Python.

Syntax

  • def decorator_function (f): pass # definierar en dekoratör som heter decorator_function

  • @decorator_function
    def dekorerad_funktion (): pass # funktionen är nu lindad (dekorerad av) dekoratörfunktion

  • decor_function = decorator_function (decor_function) # detta motsvarar användning av det syntaktiska sockret @decorator_function

parametrar

Parameter detaljer
f Funktionen som ska dekoreras (lindas)

Dekorationsfunktion

Dekoratörer ökar beteendet hos andra funktioner eller metoder. Varje funktion som tar en funktion som en parameter och returnerar en förstärkt funktion kan användas som dekoratör .

# 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.")

@ -Notationen är syntaktiskt socker som motsvarar följande:

my_function = super_secret_function(my_function)

Det är viktigt att ha detta i åtanke för att förstå hur dekoratörerna fungerar. Denna "unsugared" -syntax gör det klart varför dekoratörsfunktionen tar en funktion som ett argument och varför den ska returnera en annan funktion. Det visar också vad som skulle hända om du inte returnerar en funktion:

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

Således definierar vi vanligtvis en ny funktion i dekoratören och returnerar den. Denna nya funktion skulle först göra något som den behöver göra, sedan ringa den ursprungliga funktionen och slutligen bearbeta returvärdet. Tänk på denna enkla dekoratörsfunktion som skriver ut de argument som den ursprungliga funktionen får och sedan kallar den.

#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.

Dekoratörsklass

Som nämnts i inledningen är en dekoratör en funktion som kan tillämpas på en annan funktion för att öka dess beteende. Det syntaktiska sockret motsvarar följande: my_func = decorator(my_func) . Men tänk om decorator istället var en klass? Syntaxen skulle fortfarande fungera, förutom att nu my_func kommer att ersättas med en instans av decorator . Om denna klass implementerar magiska metoden __call__() , skulle det fortfarande vara möjligt att använda my_func som om det var en funktion:

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.

Observera att en funktion dekorerad med en klassdekorator inte längre kommer att betraktas som en "funktion" ur typkontrollperspektiv:

import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>

Dekorationsmetoder

För att dekorera metoder måste du definiera en ytterligare __get__ __-metod:

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

Inne i dekoratören.

Varning!

Klassdekoratörer producerar bara en instans för en specifik funktion så att dekorera en metod med en klassdekoratör delar samma dekoratör mellan alla instanser av den klassen:

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

Att få en dekoratör att se ut som den dekorerade funktionen

Dekoratörer ränder vanligtvis metadata eftersom de inte är desamma. Detta kan orsaka problem när du använder metaprogrammering för att dynamiskt komma åt funktionsmetadata. Metadata innehåller också funktionens dokstrings och dess namn. functools.wraps gör att den dekorerade funktionen ser ut som den ursprungliga funktionen genom att kopiera flera attribut till omslagsfunktionen.

from functools import wraps

De två metoderna för att linda in en dekoratör uppnår samma sak för att dölja att den ursprungliga funktionen har dekorerats. Det finns ingen anledning att föredra funktionsversionen framför klassversionen om du inte redan använder den ena över den andra.

Som en 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__

'testa'

Som en klass

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__

"Dokstring av test."

Dekoratör med argument (dekoratörsfabrik)

En dekoratör tar bara ett argument: funktionen som ska dekoreras. Det finns inget sätt att överföra andra argument.

Men ytterligare argument önskas ofta. Tricket är då att skapa en funktion som tar godtyckliga argument och returnerar en dekoratör.

Dekorationsfunktioner

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

Dekoratören vill säga: Hello World

Viktig notering:

Med sådana dekoratörsfabriker måste du ringa dekoratören med ett par parenteser:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError: decorator () saknas 1 obligatoriskt positionsargument: 'func'

Dekoratörskurser

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

Inne i dekoratören med argument (10,)

Skapa singleton klass med en dekoratör

En singleton är ett mönster som begränsar inställningen av en klass till en instans / ett objekt. Med hjälp av en dekoratör kan vi definiera en klass som en singleton genom att tvinga klassen att antingen returnera en befintlig instans av klassen eller skapa en ny instans (om den inte finns).

def singleton(cls):    
    instance = [None]
    def wrapper(*args, **kwargs):
        if instance[0] is None:
            instance[0] = cls(*args, **kwargs)
        return instance[0]

    return wrapper

Denna dekoratör kan läggas till i varje klassdeklaration och kommer att se till att högst en instans av klassen skapas. Efterföljande samtal kommer att returnera den redan befintliga klassinstansen.

@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

Så det spelar ingen roll om du hänvisar till klassinstansen via din lokala variabel eller om du skapar en annan "instans", du får alltid samma objekt.

Använd en dekoratör för att tidsinställa en funktion

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow