I Python er dekoratører en svært anvendelig mekanisme. Ved hjelp av dekoratører kan vi endre oppførselen til en funksjon ved å kapsle den inn i en annen. Dette gir oss muligheten til å skrive renere kode og gjenbruke funksjonalitet. Denne artikkelen gir en innføring i både bruk og opprettelse av dekoratører.
Nødvendig forkunnskap
For å forstå dekoratører i Python, kreves det en del grunnleggende kunnskap. Nedenfor har jeg listet opp noen konsepter det er nyttig å være kjent med før du leser videre. Jeg har også inkludert lenker til ressurser dersom du trenger å friske opp kunnskapen.
Grunnleggende Python
Dette er et tema som regnes som middels til avansert. Derfor bør du allerede ha god kjennskap til det grunnleggende i Python, som datatyper, funksjoner, objekter og klasser.
Du bør også ha forståelse for objektorienterte konsepter som gettere, settere og konstruktører. Hvis du ikke er komfortabel med Python, finnes det flere ressurser du kan bruke for å komme i gang.
Funksjoner som førsteklasses objekter
I tillegg til grunnleggende Python, er det også viktig å kjenne til dette mer avanserte konseptet. Funksjoner, og stort sett alt annet i Python, er objekter på lik linje med int eller streng. Dette betyr at vi kan gjøre visse ting med dem:
- Vi kan sende en funksjon som argument til en annen funksjon, akkurat som vi sender strenger eller tall.
- Funksjoner kan returneres av andre funksjoner på samme måte som andre verdier.
- Funksjoner kan lagres i variabler.
Den eneste forskjellen mellom funksjonelle objekter og andre objekter er at funksjonelle objekter inneholder den spesielle metoden __call__().
Forhåpentligvis er du nå komfortabel med forkunnskapen. La oss dykke dypere inn i selve temaet.
Hva er en Python-dekoratør?
En Python-dekoratør er rett og slett en funksjon som tar en annen funksjon som argument og returnerer en modifisert versjon av denne. Det vil si at funksjonen «foo» er en dekoratør hvis den tar funksjonen «bar» som argument og returnerer en annen funksjon «baz».
Funksjonen «baz» er en modifikasjon av «bar», i den forstand at «baz» inneholder et kall til «bar». Før og etter kallet til «bar» kan «baz» gjøre hva som helst. La oss illustrere dette med et kodeeksempel:
# 'Foo' er en dekoratør, den tar en annen funksjon, 'bar', som argument def foo(bar): # Her lager vi 'baz', en modifisert versjon av 'bar' # 'baz' vil kalle 'bar', men kan gjøre noe før og etter kallet def baz(): # Før vi kaller 'bar', skriver vi ut noe print("Noe") # Så kjører vi 'bar' ved å kalle funksjonen bar() # Deretter skriver vi ut noe annet etter at 'bar' er kjørt print("Noe annet") # Til slutt returnerer 'foo' 'baz', en modifisert versjon av 'bar' return baz
Hvordan lage en dekoratør i Python?
For å demonstrere hvordan dekoratører lages og brukes, skal vi se på et enkelt eksempel. Vi skal lage en logger-dekoratørfunksjon som logger navnet på funksjonen den dekorerer hver gang funksjonen kjøres.
Vi starter med å lage selve dekoratørfunksjonen. Denne funksjonen tar «func» som argument. «func» er funksjonen vi skal dekorere.
def create_logger(func): # Selve funksjonskroppen kommer her
Inne i dekoratørfunksjonen skal vi lage den modifiserte funksjonen som vil logge navnet på «func» før vi kjører «func».
# Inne i create_logger def modified_func(): print("Kaller:", func.__name__) func()
Deretter returnerer «create_logger»-funksjonen den modifiserte funksjonen. Hele «create_logger»-funksjonen ser dermed slik ut:
def create_logger(func): def modified_func(): print("Kaller:", func.__name__) func() return modified_func
Da er vi ferdig med å lage dekoratøren. «create_logger»-funksjonen er et enkelt eksempel på en dekoratørfunksjon. Den tar «func», som er funksjonen vi dekorerer, og returnerer en ny funksjon, «modified_func». «modified_func» logger først navnet på «func» før den kjøres.
Hvordan bruke dekoratører i Python
For å bruke dekoratøren, bruker vi syntaksen med «@», slik:
@create_logger def say_hello(): print("Hallo, verden!")
Nå kan vi kalle «say_hello()» i skriptet vårt, og resultatet vil være følgende tekst:
Kaller: say_hello "Hallo, verden!"
Men hva gjør «@create_logger»? Den bruker dekoratøren på «say_hello»-funksjonen. For å forstå hva som skjer, vil koden nedenfor oppnå det samme resultatet som å sette «@create_logger» før «say_hello».
def say_hello(): print("Hallo, verden!") say_hello = create_logger(say_hello)
Med andre ord, en måte å bruke dekoratører på er å eksplisitt kalle dekoratøren og sende inn funksjonen som argument. Den andre og mer konsise måten er å bruke «@»-syntaksen.
I denne delen har vi sett hvordan man lager dekoratører i Python.
Litt mer kompliserte eksempler
Eksemplet ovenfor var ganske enkelt. Det finnes mer komplekse eksempler, for eksempel når funksjonen vi dekorerer tar argumenter. En annen kompleks situasjon er når vi ønsker å dekorere en hel klasse. Vi skal se på begge disse tilfellene.
Når funksjonen tar argumenter
Når funksjonen du dekorerer tar argumenter, må den modifiserte funksjonen motta disse argumentene og sende dem videre når den til slutt kaller den umodifiserte funksjonen. Hvis det høres forvirrende ut, la meg forklare det med «foo»-«bar»-terminologi.
Husk at «foo» er dekoratørfunksjonen, «bar» er funksjonen vi dekorerer, og «baz» er den dekorerte «bar». I dette tilfellet vil «bar» ta argumentene, og sende dem videre til «baz» når «baz» kalles. Her er et kodeeksempel for å illustrere dette:
def foo(bar): def baz(*args, **kwargs): # Du kan gjøre noe her ___ # Så kaller vi 'bar' og sender med 'args' og 'kwargs' bar(*args, **kwargs) # Du kan også gjøre noe her ___ return baz
Hvis «*args» og «**kwargs» ser ukjente ut, er det bare pekere til henholdsvis posisjons- og nøkkelordargumenter.
Det er viktig å merke seg at «baz» har tilgang til argumentene, og kan derfor utføre validering av argumentene før «bar» kalles.
Et eksempel er hvis vi har en dekoratørfunksjon, «ensure_string», som sikrer at argumentet som sendes til en funksjon den dekorerer er en streng. Vi kan implementere den slik:
def ensure_string(func): def decorated_func(text): if type(text) is not str: raise TypeError('argumentet til ' + func.__name__ + ' må være en streng.') else: func(text) return decorated_func
Vi kan dekorere «say_hello»-funksjonen slik:
@ensure_string def say_hello(name): print('Hallo', name)
Vi kan teste koden slik:
say_hello('John') # Skal fungere fint say_hello(3) # Skal gi en feilmelding
Dette skal produsere følgende resultat:
Hallo John Traceback (most recent call last): File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # skal gi en feilmelding File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argumentet til + func._name_ + må være en streng.') TypeError: argumentet til say hello må være en streng. $0
Som forventet skrev skriptet ut «Hallo John», fordi «John» er en streng. Det genererte en feilmelding når vi prøvde å skrive ut «Hallo 3», fordi «3» ikke er en streng. «ensure_string»-dekoratøren kan brukes for å validere argumentene til enhver funksjon som krever en streng.
Dekorere en klasse
I tillegg til å dekorere funksjoner, kan vi også dekorere klasser. Når du legger til en dekoratør til en klasse, vil den dekorerte metoden erstatte klassens konstruktør/initieringsmetode (__init__).
For å returnere til «foo»-«bar»-terminologien, antar vi at «foo» er dekoratøren vår og «Bar» er klassen vi dekorerer. Da vil «foo» dekorere «Bar.__init__». Dette er nyttig hvis vi ønsker å gjøre noe før objekter av typen «Bar» opprettes.
Dette betyr at følgende kode
def foo(func): def new_func(*args, **kwargs): print('Gjør noe før initiering') func(*args, **kwargs) return new_func @foo class Bar: def __init__(self): print("I initieringsmetoden")
tilsvarer
def foo(func): def new_func(*args, **kwargs): print('Gjør noe før initiering') func(*args, **kwargs) return new_func class Bar: def __init__(self): print("I initieringsmetoden") Bar.__init__ = foo(Bar.__init__)
Faktisk vil initiering av et objekt av klassen «Bar», definert med en av de to metodene, gi samme resultat:
Gjør noe før initiering I initieringsmetoden
Eksempler på dekoratører i Python
Selv om du kan definere dine egne dekoratører, finnes det også noen innebygde dekoratører i Python. Her er noen av de vanligste du kan komme over:
@staticmethod
Den statiske metodedekoratøren brukes på en klasse for å indikere at metoden den dekorerer er en statisk metode. Statiske metoder er metoder som kan kjøres uten at klassen må initieres. I følgende kodeeksempel lager vi en klasse «Dog» med en statisk metode «bark».
class Dog: @staticmethod def bark(): print('Voff, voff!')
Nå kan «bark»-metoden nås slik:
Dog.bark()
Kjøring av denne koden vil produsere følgende:
Voff, voff!
Som nevnt i avsnittet om hvordan man bruker dekoratører, kan dekoratører brukes på to måter. «@»-syntaksen er den mest konsise, men vi kan også kalle dekoratørfunksjonen og sende inn funksjonen vi ønsker å dekorere som argument. Dette betyr at koden ovenfor oppnår det samme som koden nedenfor:
class Dog: def bark(): print('Voff, voff!') Dog.bark = staticmethod(Dog.bark)
Vi kan fortsatt bruke bark-metoden på samme måte:
Dog.bark()
Og det vil gi samme resultat:
Voff, voff!
Som du ser, er den første metoden renere, og det er tydelig at funksjonen er statisk før du har begynt å lese koden. Derfor vil vi bruke den første metoden for de resterende eksemplene, men husk at den andre metoden er et alternativ.
@classmethod
Denne dekoratøren brukes for å indikere at metoden den dekorerer er en klassemetode. Klassemetoder ligner på statiske metoder ved at de begge kan kalles uten at klassen må instansieres.
Hovedforskjellen er 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 klassemetodedekoratøren.
class Dog: @classmethod def what_are_you(cls): print("Jeg er en " + cls.__name__ + "!")
For å kjøre koden, kaller vi metoden uten å instansiere klassen:
Dog.what_are_you()
Og utskriften blir:
Jeg er en Dog!
@property
Eiendomsdekoratøren (@property) brukes for å markere en metode som en egenskap. La oss gå tilbake til eksemplet med hunden, og lage en metode som henter navnet på hunden.
class Dog: # Lager en konstruktørmetode som tar hundens navn def __init__(self, name): # Lager en privat egenskap 'name' # Doble understreker gjør attributtet privat self.__name = name @property def name(self): return self.__name
Nå kan vi få tilgang til hundens navn som en vanlig egenskap:
# Lager en instans av klassen foo = Dog('foo') # Leser name-egenskapen print("Hundens navn er:", foo.name)
Kjøring av koden vil gi følgende resultat:
Hundens navn er: foo
@property.setter
Property.setter-dekoratøren brukes for å lage en settermetode for våre egenskaper. For å bruke @property.setter-dekoratøren, erstatter du «property» med navnet på egenskapen du lager en setter for. For eksempel, hvis du lager en setter for «foo»-egenskapen, vil dekoratøren din være @foo.setter. Her er et hundeeksempel for å illustrere dette:
class Dog: # Lager en konstruktørmetode som tar hundens navn def __init__(self, name): # Lager en privat egenskap 'name' # Doble understreker gjør attributtet privat self.__name = name @property def name(self): return self.__name # Lager en setter for 'name'-egenskapen @name.setter def name(self, new_name): self.__name = new_name
For å teste setteren kan vi bruke følgende kode:
# Lager en ny hund foo = Dog('foo') # Endrer hundens navn foo.name="bar" # Skriver ut hundens nye navn print("Hundens nye navn er:", foo.name)
Kjøring av koden vil gi følgende resultat:
Hundens nye navn er: bar
Viktigheten av dekoratører i Python
Nå som vi har sett hva dekoratører er, og sett noen eksempler, kan vi diskutere hvorfor dekoratører er viktige i Python. Det er flere grunner til dette, og jeg har listet opp noen av dem 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 logging til alle våre funksjoner uten å manuelt skrive koden for hver funksjon.
- De lar deg skrive modulær kode: Igjen, tilbake til loggeksemplet, med dekoratører kan du skille kjernefunksjonaliteten, i dette tilfellet «say_hello», fra annen funksjonalitet, i dette tilfellet logging.
- De forbedrer rammeverk og biblioteker: Dekoratorer brukes mye i Python-rammeverk og biblioteker for å gi ekstra funksjonalitet. For eksempel brukes dekoratører i webrammeverk som Flask eller Django for å definere ruter, håndtere autentisering eller bruke mellomvare på spesifikke visninger.
Avsluttende ord
Dekoratører er utrolig nyttige. Du kan bruke dem for å utvide funksjonaliteten til funksjoner uten å endre selve funksjonen. Dette er nyttig når du vil tidsbestemme ytelsen til funksjoner, logge når en funksjon kalles, validere argumenter 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.
Neste skritt kan være å lese artikler om tupler og bruk av cURL i Python.