Python Language
Dekoratorzy
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()