Szukaj…


Wprowadzenie

Funkcje dekoratora to wzorce projektowe. Dynamicznie zmieniają funkcjonalność funkcji, metody lub klasy bez konieczności bezpośredniego korzystania z podklas lub zmiany kodu źródłowego dekorowanej funkcji. Przy prawidłowym stosowaniu dekoratorzy mogą stać się potężnymi narzędziami w procesie programowania. Ten temat obejmuje implementację i zastosowania funkcji dekoratora w Pythonie.

Składnia

  • def decorator_function (f): pass # definiuje dekorator o nazwie decorator_function

  • @ funkcja_dekoratora
    def decor_function (): pass # funkcja jest teraz opakowana (ozdobiona przez) decorator_function

  • @decorator_function = decorator_function (decorated_function) # jest to równoważne z użyciem cukru syntaktycznego @decorator_function

Parametry

Parametr Detale
fa Funkcja do dekoracji (zapakowana)

Funkcja dekoratora

Dekoratorzy wzmacniają zachowanie innych funkcji lub metod. Każda funkcja, która przyjmuje funkcję jako parametr i zwraca funkcję rozszerzoną, może być używana jako dekorator .

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

Adnotacja @ to cukier składniowy, który jest równoważny z następującym:

my_function = super_secret_function(my_function)

Należy o tym pamiętać, aby zrozumieć, jak działają dekoratorzy. Ta „niezasugowana” składnia wyjaśnia, dlaczego funkcja dekoratora przyjmuje funkcję jako argument i dlaczego powinna zwrócić inną funkcję. Pokazuje także, co by się stało, gdybyś nie zwrócił funkcji:

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

Dlatego zwykle definiujemy nową funkcję w dekoratorze i zwracamy ją. Ta nowa funkcja najpierw zrobi coś, co musi zrobić, a następnie wywoła funkcję oryginalną, a na koniec przetworzy wartość zwracaną. Rozważ tę prostą funkcję dekoratora, która wypisuje argumenty otrzymane przez funkcję oryginalną, a następnie wywołuje ją.

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

Klasa dekoratora

Jak wspomniano we wstępie, dekorator to funkcja, którą można zastosować do innej funkcji w celu zwiększenia jej zachowania. Cukier składniowy jest równoważny z następującym: my_func = decorator(my_func) . Ale co jeśli decorator klasą? Składnia nadal działałaby, z tym wyjątkiem, że teraz my_func zostaje zastąpione instancją klasy decorator . Jeśli ta klasa implementuje magiczną metodę __call__() , nadal byłoby możliwe użycie my_func tak, jakby była funkcją:

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.

Zauważ, że funkcja ozdobiona dekoratorem klasy nie będzie już uważana za „funkcję” z perspektywy sprawdzania typu:

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

Metody zdobienia

W przypadku metod dekorowania musisz zdefiniować dodatkową __get__ :

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

Wewnątrz dekoratora.

Ostrzeżenie!

Dekoratory klas wytwarzają tylko jedną instancję dla określonej funkcji, dlatego dekorowanie metody za pomocą dekoratora klasy będzie współdzielić ten sam dekorator między wszystkimi instancjami tej klasy:

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

Sprawienie, by dekorator wyglądał jak dekorowana funkcja

Dekoratorzy zwykle usuwają metadane funkcji, ponieważ nie są takie same. Może to powodować problemy podczas korzystania z metaprogramowania w celu dynamicznego dostępu do metadanych funkcji. Metadane obejmują również dokumentację funkcji i jej nazwę. functools.wraps sprawia, że dekorowana funkcja wygląda jak funkcja oryginalna poprzez skopiowanie kilku atrybutów do funkcji otoki.

from functools import wraps

Dwie metody owijania dekoratora pozwalają osiągnąć to samo, ukrywając oryginalną funkcję. Nie ma powodu, aby preferować wersję funkcji od wersji klasy, chyba że używasz już jednej z nich.

Jako funkcja

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__

'test'

Jako klasa

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 testu”.

Dekorator z argumentami (fabryka dekoratorów)

Dekorator bierze tylko jeden argument: funkcję do dekoracji. Nie ma możliwości przekazania innych argumentów.

Ale często potrzebne są dodatkowe argumenty. Sztuką jest wtedy utworzenie funkcji, która pobiera dowolne argumenty i zwraca dekorator.

Funkcje dekoratora

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

Dekorator chce ci powiedzieć: Hello World

Ważna uwaga:

W przypadku takich fabryk dekoratorów należy wywołać dekoratora za pomocą pary nawiasów:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError: decorator () brakuje 1 wymaganego argumentu pozycyjnego: „func”

Zajęcia dekoratorów

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

Wewnątrz dekoratora z argumentami (10,)

Stwórz klasę singleton z dekoratorem

Singleton to wzorzec, który ogranicza tworzenie instancji klasy do jednej instancji / obiektu. Za pomocą dekoratora możemy zdefiniować klasę jako singleton, zmuszając klasę do zwrócenia istniejącej instancji klasy lub utworzenia nowej instancji (jeśli nie istnieje).

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

    return wrapper

Ten dekorator można dodać do dowolnej deklaracji klasy i upewni się, że zostanie utworzona co najwyżej jedna instancja klasy. Każde kolejne wywołanie zwróci już istniejącą instancję klasy.

@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

Nie ma więc znaczenia, czy odwołujesz się do instancji klasy za pomocą zmiennej lokalnej, czy tworzysz inną „instancję”, zawsze otrzymujesz ten sam obiekt.

Używanie dekoratora do mierzenia czasu funkcji

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow