Zoeken…


Invoering

Decoratiefuncties zijn software-ontwerppatronen. Ze veranderen dynamisch de functionaliteit van een functie, methode of klasse zonder direct subklassen te gebruiken of de broncode van de gedecoreerde functie te wijzigen. Bij correct gebruik kunnen decorateurs krachtige hulpmiddelen worden in het ontwikkelingsproces. Dit onderwerp behandelt de implementatie en toepassingen van decorateurfuncties in Python.

Syntaxis

  • def decorator_function (f): pass # definieert een decorateur met de naam decorator_function

  • @decorator_function
    defdecor_function (): pass # de functie is nu ingepakt (gedecoreerd door) decorator_function

  • @decorator_function = decorator_function ( @decorator_function ) # dit komt overeen met het gebruik van de syntactische suiker @decorator_function

parameters

Parameter Details
f De functie die moet worden gedecoreerd (ingepakt)

Decorateurfunctie

Decorateurs vergroten het gedrag van andere functies of methoden. Elke functie die een functie als parameter aanneemt en een uitgebreide functie retourneert, kan als decorateur worden gebruikt.

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

De @ -notatie is syntactische suiker die overeenkomt met het volgende:

my_function = super_secret_function(my_function)

Het is belangrijk om dit in gedachten te houden om te begrijpen hoe de decorateurs werken. Deze "niet-gesuggereerde" syntaxis maakt duidelijk waarom de decorateurfunctie een functie als argument neemt en waarom deze een andere functie moet retourneren. Het laat ook zien wat er zou gebeuren als u geen functie retourneert:

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

Daarom definiëren we meestal een nieuwe functie in de decorateur en retourneren deze. Deze nieuwe functie zou eerst iets doen wat hij moet doen, dan de oorspronkelijke functie aanroepen en uiteindelijk de retourwaarde verwerken. Overweeg deze eenvoudige decorateurfunctie die de argumenten afdrukt die de oorspronkelijke functie ontvangt en deze vervolgens aanroept.

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

Decorateur klasse

Zoals vermeld in de inleiding is een decorateur een functie die op een andere functie kan worden toegepast om zijn gedrag te vergroten. De syntactische suiker is gelijk aan het volgende: my_func = decorator(my_func) . Maar wat als de decorator in plaats daarvan een klasse was? De syntaxis zou nog steeds werken, behalve dat my_func nu wordt vervangen door een exemplaar van de decorator . Als deze klasse de magische methode __call__() implementeert, zou het nog steeds mogelijk zijn om my_func te gebruiken alsof het een functie was:

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.

Merk op dat een functie versierd met een klassedecorateur niet langer als een "functie" wordt beschouwd vanuit het perspectief van typecontrole:

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

Decoratiemethoden

Voor decoratiemethoden moet u een extra __get__ -methode definiëren:

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

Binnen in de decorateur.

Waarschuwing!

Class Decorators produceren slechts één exemplaar voor een specifieke functie, dus het decoreren van een methode met een klassendecorateur deelt dezelfde decorateur tussen alle instanties van die klasse:

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

Een decorateur laten lijken op de versierde functie

Decorateurs verwijderen normaal gesproken metadata van functies omdat ze niet hetzelfde zijn. Dit kan problemen veroorzaken bij het gebruik van metaprogrammering om dynamisch toegang te krijgen tot metadata van functies. Metagegevens bevatten ook de docstrings en de naam van de functie. functools.wraps laat de gedecoreerde functie op de originele functie lijken door verschillende kenmerken naar de wrapper-functie te kopiëren.

from functools import wraps

De twee methoden voor het inpakken van een decorateur bereiken hetzelfde: het verbergen van de oorspronkelijke functie. Er is geen reden om de functieversie te verkiezen boven de klasseversie, tenzij u de ene al boven de andere gebruikt.

Als een functie

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'

Als een klas

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 van test.'

Decorateur met argumenten (decorateurfabriek)

Een decorateur neemt slechts één argument: de te decoreren functie. Er is geen manier om andere argumenten door te geven.

Maar aanvullende argumenten zijn vaak gewenst. De kunst is dan om een functie te maken die willekeurige argumenten aanneemt en een decorateur teruggeeft.

Decorateurfuncties

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

De decorateur wil je vertellen: Hallo wereld

Belangrijke notitie:

Bij dergelijke decorateurfabrieken moet u de decorateur met een paar haakjes bellen:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError: decorator () ontbreekt 1 vereist positioneel argument: 'func'

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

Binnen de decorateur met argumenten (10,)

Creëer singleton-klasse met een decorateur

Een singleton is een patroon dat de instantiatie van een klasse beperkt tot één instantie / object. Met behulp van een decorateur kunnen we een klasse als een singleton definiëren door de klasse te dwingen een bestaande instantie van de klasse te retourneren of een nieuwe instantie te maken (als deze niet bestaat).

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

    return wrapper

Deze decorateur kan aan elke klasseaangifte worden toegevoegd en zorgt ervoor dat er maximaal één instantie van de klasse wordt gemaakt. Elke volgende oproep retourneert de reeds bestaande klasse-instantie.

@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

Het maakt dus niet uit of u via uw lokale variabele naar de klasse-instantie verwijst of dat u een andere "instantie" maakt, u krijgt altijd hetzelfde object.

Een decorateur gebruiken om een functie te timen

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow