Förklarat (med exempel och användningsfall)

Python-dekoratörer är en otroligt användbar konstruktion i Python. Med hjälp av dekoratörer i Python kan vi ändra beteendet hos en funktion genom att linda in den i en annan funktion. Dekoratörer gör det möjligt för oss att skriva renare kod och dela funktionalitet. Den här artikeln är en handledning om inte bara hur man använder dekoratörer utan hur man skapar dem.

Förkunskaper

Ämnet dekoratörer i Python kräver viss bakgrundskunskap. Nedan har jag listat några begrepp som du redan borde vara bekant med för att förstå den här handledningen. Jag har även länkat resurser där du kan fräscha upp begreppen om det skulle behövas.

Grundläggande Python

Det här ämnet är ett mer mellanliggande/avancerat ämne. Som ett resultat, innan du försöker lära dig, bör du redan vara bekant med grunderna i Python, såsom datatyper, funktioner, objekt och klasser.

Du bör också förstå några objektorienterade begrepp som getters, sättare och konstruktörer. Om du inte är bekant med programmeringsspråket Python, här är några resurser för att komma igång.

Funktioner är förstklassiga medborgare

Förutom grundläggande Python bör du också vara medveten om detta mer avancerade koncept i Python. Funktioner, och i stort sett allt annat i Python, är objekt som int eller sträng. Eftersom de är föremål kan du göra några saker med dem, nämligen:

  • Du kan skicka en funktion som ett argument till en annan funktion på samma sätt som du skickar en sträng eller int som ett funktionsargument.
  • Funktioner kan också returneras av andra funktioner som du skulle returnera andra sträng- eller int-värden.
  • Funktioner kan lagras i variabler

Faktum är att den enda skillnaden mellan funktionella objekt och andra objekt är att funktionella objekt innehåller den magiska metoden __call__().

Förhoppningsvis är du vid denna tidpunkt bekväm med förutsättningskunskapen. Vi kan börja diskutera huvudämnet.

Vad är en Python Decorator?

En Python decorator är helt enkelt en funktion som tar in en funktion som ett argument och returnerar en modifierad version av funktionen som skickades in. Funktionen foo är med andra ord en dekorator om den som argument tar in funktionsfältet och returnerar en annan funktion baz.

Funktionen baz är en modifiering av bar i den meningen att inom baz-kroppen finns det ett anrop till funktionsfältet. Men före och efter samtalet till baren kan baz göra vad som helst. Det var en munfull; här är lite kod för att illustrera situationen:

# Foo is a decorator, it takes in another function, bar as an argument
def foo(bar):

    # Here we create baz, a modified version of bar
    # baz will call bar but can do anything before and after the function call
    def baz():

        # Before calling bar, we print something
        print("Something")

        # Then we run bar by making a function call
        bar()

        # Then we print something else after running bar
        print("Something else")

    # Lastly, foo returns baz, a modified version of bar
    return baz

Hur skapar man en dekoratör i Python?

För att illustrera hur dekoratörer skapas och används i Python ska jag illustrera detta med ett enkelt exempel. I det här exemplet kommer vi att skapa en loggerdekoratörsfunktion som loggar namnet på funktionen den dekorerar varje gång den funktionen körs.

För att komma igång skapade vi dekorationsfunktionen. Dekoratören tar in func som argument. func är funktionen vi inreder.

def create_logger(func):
    # The function body goes here

Inuti dekorationsfunktionen kommer vi att skapa vår modifierade funktion som loggar namnet på func innan vi kör func.

# Inside create_logger
def modified_func():
    print("Calling: ", func.__name__)
    func()

Därefter kommer create_logger-funktionen att returnera den modifierade funktionen. Som ett resultat kommer hela vår create_logger-funktion att se ut så här:

def create_logger(func):
    def modified_func():
        print("Calling: ", func.__name__)
        func()

    return modified_function

Vi är klara med att skapa dekoratören. Create_logger-funktionen är ett enkelt exempel på en dekorationsfunktion. Den tar in func, vilket är funktionen vi dekorerar, och returnerar en annan funktion, modified_func. modified_func loggar först namnet på func, innan func körs.

Hur man använder dekoratörer i Python

För att använda vår dekorator använder vi @-syntaxen så här:

@create_logger
def say_hello():
    print("Hello, World!")

Nu kan vi anropa say_hello() i vårt skript, och utdata ska vara följande text:

Calling:  say_hello
"Hello, World"

Men vad gör @create_logger? Tja, det är att applicera dekoratören på vår say_hello-funktion. För att bättre förstå vad som görs, skulle koden omedelbart under detta stycke uppnå samma resultat som att sätta @create_logger före say_hello.

def say_hello():
    print("Hello, World!")

say_hello = create_logger(say_hello)

Med andra ord, ett sätt att använda dekoratörer i Python är att uttryckligen anropa dekoratören som passerar i funktionen som vi gjorde i koden ovan. Det andra och mer kortfattade sättet är att använda @-syntaxen.

I det här avsnittet behandlade vi hur man skapar Python-dekoratörer.

Lite mer komplicerade exempel

Ovanstående exempel var ett enkelt fall. Det finns lite mer komplexa exempel som när funktionen vi dekorerar tar in argument. En annan mer komplicerad situation är när man vill inreda en hel klass. Jag kommer att täcka båda dessa situationer här.

När funktionen tar in argument

När funktionen du dekorerar tar in argument, bör den modifierade funktionen ta emot argumenten och skicka dem när den så småningom gör anropet till den omodifierade funktionen. Om det låter förvirrande, låt mig förklara i foo-bar-termer.

Kom ihåg att foo är dekorationsfunktionen, bar är funktionen vi dekorerar och baz är den dekorerade baren. I så fall kommer bar att ta in argumenten och skicka dem till baz under samtalet till baz. Här är ett kodexempel för att befästa konceptet:

def foo(bar):
    def baz(*args, **kwargs):
        # You can do something here
        ___
        # Then we make the call to bar, passing in args and kwargs
        bar(*args, **kwargs)
        # You can also do something here
        ___

    return baz

Om *args och **kwargs ser obekanta ut; de är helt enkelt pekare till positions- respektive nyckelordsargumenten.

Det är viktigt att notera att baz har tillgång till argumenten och därför kan utföra viss validering av argumenten innan du anropar bar.

Ett exempel skulle vara om vi hade en dekorationsfunktion, sure_string som skulle säkerställa att argumentet som skickas till en funktion som den dekorerar är en sträng; vi skulle implementera det så här:

def ensure_string(func):
    def decorated_func(text):
        if type(text) is not str:
             raise TypeError('argument to ' + func.__name__ + ' must be a string.')
        else:
             func(text)

    return decorated_func

Vi skulle kunna dekorera say_hello-funktionen så här:

@ensure_string
def say_hello(name):
    print('Hello', name)

Sedan kan vi testa koden med detta:

say_hello('John') # Should run just fine
say_hello(3) # Should throw an exception

Och det borde producera följande utdata:

Hello John
Traceback (most recent call last):
   File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception
   File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.')
TypeError: argument to say hello must be a string. $0

Som väntat lyckades manuset skriva ut ”Hello John” eftersom ”John” är en sträng. Det gav ett undantag när man försökte skriva ut ”Hello 3” eftersom ”3” inte var en sträng. Decoratorn sure_string kan användas för att validera argumenten för alla funktioner som kräver en sträng.

Att dekorera en klass

Förutom att bara inreda funktioner kan vi även inreda klasser. När du lägger till en dekoratör till en klass ersätter den dekorerade metoden klassens konstruktor/initiatormetod(__init__).

Om vi ​​går tillbaka till foo-bar, anta att foo är vår dekoratör och Bar är klassen vi dekorerar, då kommer foo att dekorera Bar.__init__. Detta kommer att vara användbart om vi vill göra något innan objekt av typen Bar instansieras.

Det betyder att följande kod

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

@foo
class Bar:
    def __init__(self):
        print("In initiator")

Är ekvivalent med

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

class Bar:
    def __init__(self):
        print("In initiator")


Bar.__init__ = foo(Bar.__init__)

Faktum är att instansiering av ett objekt av klass Bar, definierat med någon av de två metoderna, borde ge dig samma utdata:

Doing some stuff before instantiation
In initiator

Exempel på dekoratörer i Python

Även om du kan definiera dina egna dekoratörer, finns det några som redan är inbyggda i Python. Här är några av de vanligaste dekoratörerna du kan stöta på i Python:

@statisk metod

Den statiska metoden används på en klass för att indikera att metoden den dekorerar är en statisk metod. Statiska metoder är metoder som kan köras utan att behöva instansiera klassen. I följande kodexempel skapar vi en klass Dog med en statisk metod bark.

class Dog:
    @staticmethod
    def bark():
        print('Woof, woof!')

Nu kan barkmetoden nås så här:

Dog.bark()

Och att köra koden skulle producera följande utdata:

Woof, woof!

Som jag nämnde i avsnittet om hur man använder dekoratorer kan dekoratorer användas på två sätt. @-syntaxen är den mer koncisa och är en av de två. Den andra metoden är att anropa dekorationsfunktionen och skicka in funktionen vi vill dekorera som ett argument. Det betyder att koden ovan uppnår samma sak som koden nedan:

class Dog:
    def bark():
        print('Woof, woof!')

Dog.bark = staticmethod(Dog.bark)

Och vi kan fortfarande använda barkmetoden på samma sätt

Dog.bark()

Och det skulle producera samma resultat

Woof, woof!

Som du kan se är den första metoden renare och det är mer uppenbart att funktionen är en statisk funktion innan du ens har börjat läsa koden. Som ett resultat, för de återstående exemplen, kommer jag att använda den första metoden. Men kom bara ihåg att den andra metoden är ett alternativ.

@klassmetod

Denna dekorator används för att indikera att metoden den dekorerar är en klassmetod. Klassmetoder liknar statiska metoder genom att de båda inte kräver att klassen instansieras innan de kan anropas.

Den största skillnaden är dock att klassmetoder har tillgång till klassattribut medan statiska metoder inte har det. Detta beror på att Python automatiskt skickar klassen som det första argumentet till en klassmetod när den anropas. För att skapa en klassmetod i Python kan vi använda klassmetoden dekoratör.

class Dog:
    @classmethod
    def what_are_you(cls):
        print("I am a " + cls.__name__ + "!")

För att köra koden anropar vi helt enkelt metoden utan att instansiera klassen:

Dog.what_are_you()

Och resultatet är:

I am a Dog!

@fast egendom

Fastighetsdekoratören används för att beteckna en metod som en egenskapssättare. Om vi ​​går tillbaka till vårt hundexempel, låt oss skapa en metod som hämtar namnet på hunden.

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

Nu kan vi komma åt hundens namn som en vanlig egendom,

# Creating an instance of the class
foo = Dog('foo')

# Accessing the name property
print("The dog's name is:", foo.name)

Och resultatet av att köra koden skulle bli

The dog's name is: foo

@fastighet.setter

Property.setter dekoratören används för att skapa en sättermetod för våra fastigheter. För att använda @property.setter-dekoratören ersätter du egenskapen med namnet på fastigheten som du skapar en setter för. Om du till exempel skapar en sättare för metoden för egenskapen foo, kommer din dekoratör att vara @foo.setter. Här är ett hundexempel för att illustrera:

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

    # Creating a setter for our name property
    @name.setter
    def name(self, new_name):
        self.__name = new_name

För att testa inställaren kan vi använda följande kod:

# Creating a new dog
foo = Dog('foo')

# Changing the dog's name
foo.name="bar"

# Printing the dog's name to the screen
print("The dog's new name is:", foo.name)

Att köra koden kommer att producera följande utdata:

The dogs's new name is: bar

Betydelsen av dekoratörer i Python

Nu när vi har täckt vad dekoratörer är, och du har sett några exempel på dekoratörer, kan vi diskutera varför dekoratörer är viktiga i Python. Dekoratörer är viktiga av flera anledningar. Några av dem har jag listat nedan:

  • De möjliggör återanvändning av kod: I loggningsexemplet ovan kan vi använda @create_logger på vilken funktion vi vill. Detta gör att vi kan lägga till loggningsfunktioner till alla våra funktioner utan att manuellt skriva det för varje funktion.
  • De låter dig skriva modulär kod: Återigen, om du går tillbaka till loggningsexemplet, med dekoratörer, kan du separera kärnfunktionen, i det här fallet say_hello från den andra funktionaliteten du behöver, i det här fallet loggning.
  • De förbättrar ramverk och bibliotek: Dekoratorer används flitigt i Python-ramverk och bibliotek för att tillhandahålla ytterligare funktionalitet. Till exempel, i webbramverk som Flask eller Django, används dekoratörer för att definiera rutter, hantera autentisering eller tillämpa mellanprogram på specifika vyer.

Slutord

Dekoratörer är otroligt användbara; du kan använda dem för att utöka funktioner utan att ändra deras funktionalitet. Detta är användbart när du vill tajma prestanda för funktioner, logga när en funktion anropas, validera argument innan en funktion anropas eller verifiera behörigheter innan en funktion körs. När du väl förstår dekoratörer kommer du att kunna skriva kod på ett renare sätt.

Därefter kanske du vill läsa våra artiklar om tupler och användning av cURL i Python.