Python Language
dekoratörer
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örfunktiondecor_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()