Forklaret (med eksempler og brugssager)

Python dekoratorer er en utrolig nyttig konstruktion i Python. Ved at bruge dekoratorer i Python kan vi ændre en funktions adfærd ved at pakke den ind i en anden funktion. Dekoratører gør det muligt for os at skrive renere kode og dele funktionalitet. Denne artikel er en vejledning om ikke kun, hvordan man bruger dekoratører, men hvordan man laver dem.

Forudsætningsviden

Emnet dekoratører i Python kræver en vis baggrundsviden. Nedenfor har jeg listet nogle begreber, du allerede burde være bekendt med for at give mening i denne tutorial. Jeg har også knyttet ressourcer, hvor du kan friske op på begreberne, hvis det er nødvendigt.

Grundlæggende Python

Dette emne er et mere mellemliggende/avanceret emne. Som et resultat, før du forsøger at lære, bør du allerede være bekendt med det grundlæggende i Python, såsom datatyper, funktioner, objekter og klasser.

Du bør også forstå nogle objektorienterede begreber såsom gettere, sættere og konstruktører. Hvis du ikke er bekendt med programmeringssproget Python, er her nogle ressourcer til at komme i gang.

Funktioner er førsteklasses borgere

Ud over grundlæggende Python bør du også være opmærksom på dette mere avancerede koncept i Python. Funktioner, og stort set alt andet i Python, er objekter som int eller streng. Fordi de er objekter, kan du gøre et par ting med dem, nemlig:

  • Du kan videregive en funktion som et argument til en anden funktion på samme måde, som du sender en streng eller int som et funktionsargument.
  • Funktioner kan også returneres af andre funktioner, ligesom du ville returnere andre streng- eller int-værdier.
  • Funktioner kan gemmes i variable

Faktisk er den eneste forskel mellem funktionelle objekter og andre objekter funktionelle objekter indeholder den magiske metode __call__().

Forhåbentlig er du på dette tidspunkt fortrolig med den forudgående viden. Vi kan begynde at diskutere hovedemnet.

Hvad er en Python Decorator?

En Python-dekorator er simpelthen en funktion, der tager en funktion ind som et argument og returnerer en ændret version af den funktion, der blev sendt ind. Funktionen foo er med andre ord en dekorator, hvis den som argument tager funktionslinjen ind. og returnerer en anden funktion baz.

Funktionen baz er en modifikation af bar i den forstand, at der inden for baz’s brødtekst er et kald til funktionslinjen. Men før og efter opkaldet til baren kan baz gøre alt. Det var en mundfuld; her er en kode til at illustrere 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

Hvordan opretter man en dekoratør i Python?

For at illustrere, hvordan dekoratører skabes og bruges i Python, vil jeg illustrere dette med et simpelt eksempel. I dette eksempel vil vi oprette en logger dekorationsfunktion, der vil logge navnet på den funktion, den dekorerer, hver gang den funktion kører.

  Flint bringer hurtig deling af sociale medier og postplanlægning til Chrome

For at komme i gang har vi lavet dekorationsfunktionen. Dekoratøren tager func ind som argument. func er den funktion, vi indretter.

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

Inde i dekorationsfunktionen skal vi lave vores modificerede funktion, der logger navnet på func, før vi kører func.

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

Dernæst vil create_logger-funktionen returnere den ændrede funktion. Som et resultat vil hele vores create_logger-funktion se sådan ud:

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

    return modified_function

Vi er færdige med at skabe dekoratøren. Create_logger-funktionen er et simpelt eksempel på en dekorationsfunktion. Den tager func ind, som er den funktion, vi dekorerer, og returnerer en anden funktion, modified_func. modified_func logger først navnet på func, før func køres.

Sådan bruger du dekoratører i Python

For at bruge vores dekorator bruger vi @-syntaksen sådan:

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

Nu kan vi kalde say_hello() i vores script, og outputtet skal være følgende tekst:

Calling:  say_hello
"Hello, World"

Men hvad laver @create_logger? Nå, det er at anvende dekoratøren til vores say_hello-funktion. For bedre at forstå, hvad der gør, ville koden umiddelbart under dette afsnit opnå det samme resultat som at sætte @create_logger før say_hello.

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

say_hello = create_logger(say_hello)

Med andre ord, en måde at bruge dekoratører på i Python er eksplicit at kalde dekoratøren, der passerer i funktionen, som vi gjorde i koden ovenfor. Den anden og mere kortfattede måde er at bruge @-syntaksen.

I dette afsnit dækkede vi, hvordan man opretter Python-dekoratører.

Lidt mere komplicerede eksempler

Ovenstående eksempel var et simpelt tilfælde. Der er lidt mere komplekse eksempler som f.eks. når den funktion, vi dekorerer, tager argumenter ind. En anden mere kompliceret situation er, når du vil dekorere en hel klasse. Jeg vil dække begge disse situationer her.

Når funktionen tager argumenter ind

Når den funktion, du dekorerer, tager argumenter ind, bør den ændrede funktion modtage argumenterne og sende dem, når den til sidst kalder den umodificerede funktion. Hvis det lyder forvirrende, så lad mig forklare i foo-bar-termer.

Husk, at foo er dekorationsfunktionen, baren er den funktion, vi dekorerer, og baz er den dekorerede bar. I så fald vil bar tage argumenterne ind og videregive dem til baz under opkaldet til baz. Her er et kodeeksempel til at styrke 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

Hvis *args og **kwargs ser ukendte ud; de er blot pejlemærker til henholdsvis positions- og nøgleordsargumenterne.

Det er vigtigt at bemærke, at baz har adgang til argumenterne og derfor kan udføre en vis validering af argumenterne, før de kalder bar.

Et eksempel ville være, hvis vi havde en dekorationsfunktion, sure_string, der ville sikre, at argumentet, der sendes til en funktion, den dekorerer, er en streng; vi ville implementere det sådan:

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 kunne dekorere say_hello-funktionen sådan:

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

Så kunne vi teste koden ved at bruge denne:

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

Og det skulle producere følgende output:

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 forventet lykkedes det for manuskriptet at udskrive ‘Hello John’, fordi ‘John’ er en streng. Det gav en undtagelse, når du forsøgte at udskrive ‘Hej 3’, fordi ‘3’ ikke var en streng. sure_string-dekoratoren kunne bruges til at validere argumenterne for enhver funktion, der kræver en streng.

  Sådan aktiverer du højprioritetsmeddelelser til Gmail

Udsmykning af en klasse

Udover blot udsmykning af funktioner, kan vi også dekorere klasser. Når du tilføjer en dekorator til en klasse, erstatter den dekorerede metode klassens konstruktør/initiator-metode(__init__).

Går tilbage til foo-bar, antag at foo er vores dekoratør, og Bar er klassen, vi dekorerer, så vil foo dekorere Bar.__init__. Dette vil være nyttigt, hvis vi ønsker at gøre noget, før objekter af typen Bar instansieres.

Det betyder, at følgende kode

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

Er svarende til

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

Faktisk burde instansiering af et objekt af klasse Bar, defineret ved hjælp af en af ​​de to metoder, give dig det samme output:

Doing some stuff before instantiation
In initiator

Eksempel på dekoratører i Python

Mens du kan definere dine egne dekoratører, er der nogle, der allerede er indbygget i Python. Her er nogle af de almindelige dekoratører, du kan støde på i Python:

@statisk metode

Den statiske metode bruges på en klasse for at indikere, at den metode, den dekorerer, er en statisk metode. Statiske metoder er metoder, der kan køre uden behov for at instansiere klassen. I det følgende kodeeksempel opretter vi en klasse Hund med en statisk metodebark.

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

Nu kan barkmetoden tilgås sådan:

Dog.bark()

Og at køre koden ville producere følgende output:

Woof, woof!

Som jeg nævnte i afsnittet om Sådan bruges dekoratører, kan dekoratører bruges på to måder. @-syntaksen er den mere kortfattede og er den ene af de to. Den anden metode er at kalde dekorationsfunktionen, idet vi indsætter den funktion, vi ønsker at dekorere, som et argument. Det betyder, at koden ovenfor opnår det samme som koden nedenfor:

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

Dog.bark = staticmethod(Dog.bark)

Og vi kan stadig bruge barkmetoden på samme måde

Dog.bark()

Og det ville producere det samme output

Woof, woof!

Som du kan se, er den første metode renere, og det er mere indlysende, at funktionen er en statisk funktion, før du overhovedet er begyndt at læse koden. Som et resultat, for de resterende eksempler, vil jeg bruge den første metode. Men husk bare, at den anden metode er et alternativ.

  Her er alt hvad du bør vide

@klassemetode

Denne dekorator bruges til at indikere, at den metode, den dekorerer, er en klassemetode. Klassemetoder ligner statiske metoder, idet de begge ikke kræver, at klassen instansieres, før de kan kaldes.

Den største forskel er dog, at klassemetoder har adgang til klasseattributter, mens statiske metoder ikke har. Dette skyldes, at Python automatisk sender klassen som det første argument til en klassemetode, når den kaldes. For at lave en klassemetode i Python kan vi bruge klassemetodedekoratøren.

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

For at køre koden kalder vi blot metoden uden at instansiere klassen:

Dog.what_are_you()

Og outputtet er:

I am a Dog!

@ejendom

Ejendomsdekoratøren bruges til at betegne en metode som ejendomssætter. Går tilbage til vores hundeeksempel, lad os skabe en metode, der henter navnet 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 få adgang til hundens navn som en normal ejendom,

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

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

Og resultatet af at køre koden ville være

The dog's name is: foo

@ejendom.setter

Property.setter dekoratøren bruges til at skabe en setter metode til vores ejendomme. For at bruge @property.setter dekoratøren erstatter du ejendom med navnet på den ejendom, du opretter en setter for. For eksempel, hvis du opretter en sætter til metoden for ejendommen foo, vil din dekoratør være @foo.setter. Her er et hundeeksempel til illustration:

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

For at teste sætteren kan vi bruge følgende kode:

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

Kørsel af koden vil producere følgende output:

The dogs's new name is: bar

Betydningen af ​​dekoratører i Python

Nu hvor vi har dækket, hvad dekoratører er, og du har set nogle eksempler på dekoratører, kan vi diskutere, hvorfor dekoratører er vigtige i Python. Dekoratører er vigtige af flere grunde. Nogle af dem har jeg listet nedenfor:

  • De muliggør genbrugbar kode: I logningseksemplet ovenfor kunne vi bruge @create_logger på enhver funktion, vi ønsker. Dette giver os mulighed for at tilføje logningsfunktionalitet til alle vores funktioner uden manuelt at skrive det for hver funktion.
  • De giver dig mulighed for at skrive modulær kode: Igen, gå tilbage til logningseksemplet, med dekoratører, kan du adskille kernefunktionen, i dette tilfælde say_hello fra den anden funktionalitet, du har brug for, i dette tilfælde logning.
  • De forbedrer rammer og biblioteker: Dekoratorer bruges i vid udstrækning i Python-rammer og biblioteker for at give yderligere funktionalitet. For eksempel, i web-frameworks som Flask eller Django, bruges dekoratører til at definere ruter, håndtere godkendelse eller anvende middleware til specifikke visninger.

Afsluttende ord

Dekoratører er utroligt nyttige; du kan bruge dem til at udvide funktioner uden at ændre deres funktionalitet. Dette er nyttigt, når du vil tidsindstille funktionernes ydeevne, logge, når en funktion kaldes, validere argumenter, før du kalder en funktion, eller verificere tilladelser, før en funktion køres. Når du forstår dekoratører, vil du være i stand til at skrive kode på en renere måde.

Dernæst vil du måske læse vores artikler om tuples og brug af cURL i Python.