Forklart (med eksempler og brukstilfeller)

Python-dekoratorer er en utrolig nyttig konstruksjon i Python. Ved å bruke dekoratorer i Python kan vi endre oppførselen til en funksjon ved å pakke den inn i en annen funksjon. Dekoratører gjør det mulig for oss å skrive renere kode og dele funksjonalitet. Denne artikkelen er en veiledning om ikke bare hvordan du bruker dekoratører, men hvordan du lager dem.

Forutsetningskunnskap

Temaet dekoratører i Python krever litt bakgrunnskunnskap. Nedenfor har jeg listet opp noen konsepter du allerede bør være kjent med for å gi mening i denne opplæringen. Jeg har også koblet ressurser der du kan friske opp konseptene hvis det skulle være nødvendig.

Grunnleggende Python

Dette emnet er et mer middels/avansert emne. Som et resultat, før du prøver å lære, bør du allerede være kjent med det grunnleggende om Python, for eksempel datatyper, funksjoner, objekter og klasser.

Du bør også forstå noen objektorienterte konsepter som gettere, settere og konstruktører. Hvis du ikke er kjent med programmeringsspråket Python, her er noen ressurser for å komme i gang.

Funksjoner er førsteklasses borgere

I tillegg til grunnleggende Python, bør du også være klar over dette mer avanserte konseptet i Python. Funksjoner, og stort sett alt annet i Python, er objekter som int eller streng. Fordi de er objekter, kan du gjøre et par ting med dem, nemlig:

  • Du kan sende en funksjon som et argument til en annen funksjon på samme måte som du sender en streng eller int som et funksjonsargument.
  • Funksjoner kan også returneres av andre funksjoner som du ville returnert andre streng- eller int-verdier.
  • Funksjoner kan lagres i variabler

Faktisk er den eneste forskjellen mellom funksjonelle objekter og andre objekter funksjonelle objekter inneholder den magiske metoden __call__().

Forhåpentligvis, på dette tidspunktet, er du komfortabel med forutsetningskunnskapen. Vi kan begynne å diskutere hovedtemaet.

Hva er en Python Decorator?

En Python-dekorator er ganske enkelt en funksjon som tar inn en funksjon som et argument og returnerer en modifisert versjon av funksjonen som ble sendt inn. Med andre ord, funksjonen foo er en dekorator hvis den tar inn funksjonslinjen som et argument. og returnerer en annen funksjon baz.

Funksjonen baz er en modifikasjon av bar i den forstand at i hoveddelen av baz er det et kall til funksjonslinjen. Men før og etter samtalen til baren kan baz gjøre alt. Det var en munnfull; her er litt kode for å illustrere situasjonen:

# 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 lage en dekoratør i Python?

For å illustrere hvordan dekoratører lages og brukes i Python, skal jeg illustrere dette med et enkelt eksempel. I dette eksemplet vil vi lage en logger-dekoratorfunksjon som vil logge navnet på funksjonen den dekorerer hver gang funksjonen kjører.

  Hvordan holde datamaskinen din trygg fra zip-bomber

For å komme i gang har vi laget dekorasjonsfunksjonen. Dekoratøren tar i func som argument. func er funksjonen vi dekorerer.

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

Inne i dekorasjonsfunksjonen skal vi lage vår modifiserte funksjon som vil logge navnet på func før vi kjører func.

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

Deretter vil create_logger-funksjonen returnere den modifiserte funksjonen. Som et resultat vil hele create_logger-funksjonen vår se slik ut:

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

    return modified_function

Vi er ferdige med å lage dekoratøren. Create_logger-funksjonen er et enkelt eksempel på en dekorasjonsfunksjon. Den tar inn func, som er funksjonen vi dekorerer, og returnerer en annen funksjon, modified_func. modified_func logger først navnet på func, før func kjøres.

Hvordan bruke dekoratører i Python

For å bruke dekoratoren vår bruker vi @-syntaksen slik:

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

Nå kan vi kalle say_hello() i skriptet vårt, og utdataene skal være følgende tekst:

Calling:  say_hello
"Hello, World"

Men hva gjør @create_logger? Vel, det bruker dekoratøren til si_hei-funksjonen vår. For bedre å forstå hva som gjør, vil koden rett under dette avsnittet oppnå samme resultat som å sette @create_logger før say_hello.

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

say_hello = create_logger(say_hello)

Med andre ord, en måte å bruke dekoratører på i Python er å eksplisitt kalle dekoratøren som passerer funksjonen som vi gjorde i koden ovenfor. Den andre og mer konsise måten er å bruke @-syntaksen.

I denne delen dekket vi hvordan du lager Python-dekoratører.

Litt mer kompliserte eksempler

Eksempelet ovenfor var et enkelt tilfelle. Det er litt mer komplekse eksempler som når funksjonen vi dekorerer tar inn argumenter. En annen mer komplisert situasjon er når du ønsker å dekorere en hel klasse. Jeg skal dekke begge disse situasjonene her.

Når funksjonen tar inn argumenter

Når funksjonen du dekorerer tar inn argumenter, skal den modifiserte funksjonen motta argumentene og sende dem når den til slutt kaller den umodifiserte funksjonen. Hvis det høres forvirrende ut, la meg forklare i foo-bar-termer.

Husk at foo er dekorasjonsfunksjonen, bar er funksjonen vi dekorerer og baz er den dekorerte baren. I så fall vil bar ta inn argumentene og sende dem til baz under samtalen til baz. Her er et kodeeksempel for å styrke konseptet:

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 ukjente ut; de er rett og slett pekepinner til henholdsvis posisjons- og nøkkelordargumentene.

Det er viktig å merke seg at baz har tilgang til argumentene og kan derfor utføre en viss validering av argumentene før du kaller bar.

Et eksempel ville være hvis vi hadde en dekoratorfunksjon, sure_string som ville sikre at argumentet som sendes til en funksjon den dekorerer er en streng; vi vil implementere det slik:

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 kan dekorere si_hei-funksjonen slik:

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

Da kan vi teste koden ved å bruke denne:

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

Og den skal produsere følgende utgang:

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 klarte manuset å skrive ut «Hello John» fordi «John» er en streng. Det ga et unntak når du prøvde å skrive ut «Hello 3» fordi «3» ikke var en streng. sure_string-dekoratoren kan brukes til å validere argumentene til enhver funksjon som krever en streng.

  9 beste moodboard-skapere for å reflektere ideer og tanker

Dekorere en klasse

I tillegg til bare å dekorere funksjoner, kan vi også dekorere klasser. Når du legger til en dekorator til en klasse, erstatter den dekorerte metoden klassens konstruktør/initiatormetode(__init__).

Går tilbake til foo-bar, anta at foo er dekoratøren vår og Bar er klassen vi dekorerer, så vil foo dekorere Bar.__init__. Dette vil være nyttig hvis vi ønsker å gjøre noe før objekter av typen Bar instansieres.

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

Tilsvarer

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 bør instansiering av et objekt av klasse Bar, definert ved hjelp av en av de to metodene, gi deg samme utdata:

Doing some stuff before instantiation
In initiator

Eksempel på dekoratører i Python

Mens du kan definere dine egne dekoratører, er det noen som allerede er innebygd i Python. Her er noen av de vanlige dekoratørene du kan støte på i Python:

@statisk metode

Den statiske metoden brukes på en klasse for å indikere at metoden den dekorerer er en statisk metode. Statiske metoder er metoder som kan kjøres uten behov for å instansiere klassen. I følgende kodeeksempel lager vi en klasse Dog med en statisk metodebjeff.

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

Nå kan barkmetoden nås slik:

Dog.bark()

Og å kjøre koden ville produsere følgende utgang:

Woof, woof!

Som jeg nevnte i avsnittet om Hvordan bruke dekoratorer, kan dekoratorer brukes på to måter. @-syntaksen er den mer konsise og er en av de to. Den andre metoden er å kalle dekoratorfunksjonen, ved å sende inn funksjonen vi ønsker å dekorere som et argument. Det betyr at koden ovenfor oppnår det samme som koden nedenfor:

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

Dog.bark = staticmethod(Dog.bark)

Og vi kan fortsatt bruke barkmetoden på samme måte

Dog.bark()

Og det ville gi samme utgang

Woof, woof!

Som du kan se, er den første metoden renere og det er mer åpenbart at funksjonen er en statisk funksjon før du har begynt å lese koden. Som et resultat, for de resterende eksemplene, vil jeg bruke den første metoden. Men husk bare at den andre metoden er et alternativ.

@klassemetode

Denne dekoratoren brukes til å indikere at metoden den dekorerer er en klassemetode. Klassemetoder ligner statiske metoder ved at de begge ikke krever at klassen instansieres før de kan kalles.

  Opprett en gjestekonto på din iPhone med gjestemodus

Hovedforskjellen er imidlertid at klassemetoder har tilgang til klasseattributter mens statiske metoder ikke har det. Dette er fordi Python automatisk sender klassen som det første argumentet til en klassemetode hver gang den kalles. For å lage en klassemetode i Python kan vi bruke klassemetodedekoratoren.

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

For å kjøre koden kaller vi bare metoden uten å instansiere klassen:

Dog.what_are_you()

Og utgangen er:

I am a Dog!

@eiendom

Eiendomsdekoratøren brukes til å merke en metode som en egenskapssetter. Gå tilbake til hundeeksemplet vårt, la oss lage en metode som 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

Nå kan vi få tilgang til hundens navn som en vanlig eiendom,

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

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

Og resultatet av å kjøre koden ville være

The dog's name is: foo

@property.setter

Property.setter-dekoratøren brukes til å lage en settermetode for våre eiendommer. For å bruke @property.setter-dekoratøren, erstatter du eiendom med navnet på eiendommen du oppretter en setter for. Hvis du for eksempel lager en setter for metoden for egenskapen foo, vil dekoratøren din være @foo.setter. Her er et hundeksempel for å illustrere:

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 å teste setteren kan vi bruke 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)

Å kjøre koden vil produsere følgende utgang:

The dogs's new name is: bar

Viktigheten av dekoratører i Python

Nå som vi har dekket hva dekoratører er, og du har sett noen eksempler på dekoratører, kan vi diskutere hvorfor dekoratører er viktige i Python. Dekoratører er viktige av flere grunner. Noen av dem har jeg listet opp nedenfor:

  • De muliggjør gjenbruk av kode: I loggeksemplet ovenfor kan vi bruke @create_logger på hvilken som helst funksjon vi ønsker. Dette lar oss legge til loggingsfunksjonalitet til alle funksjonene våre uten å manuelt skrive det for hver funksjon.
  • De lar deg skrive modulær kode: Igjen, gå tilbake til loggeksemplet, med dekoratører, kan du skille kjernefunksjonen, i dette tilfellet say_hello fra den andre funksjonaliteten du trenger, i dette tilfellet logging.
  • De forbedrer rammeverk og biblioteker: Dekoratorer er mye brukt i Python-rammeverk og biblioteker for å gi ekstra funksjonalitet. For eksempel, i nettrammeverk som Flask eller Django, brukes dekoratører for å definere ruter, håndtere autentisering eller bruke mellomvare på spesifikke visninger.

Siste ord

Dekoratører er utrolig nyttige; du kan bruke dem til å utvide funksjoner uten å endre funksjonaliteten. Dette er nyttig når du vil tidsbestemme ytelsen til funksjoner, logge når en funksjon kalles, validere argumenter før du kaller en funksjon, eller verifisere tillatelser før en funksjon kjøres. Når du forstår dekoratører, vil du kunne skrive kode på en renere måte.

Deretter vil du kanskje lese artiklene våre om tuples og bruk av cURL i Python.