Razumevanje Python Dekoratera
Python dekorateri predstavljaju izuzetno koristan alat u programskom jeziku Python. Oni omogućavaju izmenu ponašanja funkcija tako što ih obavijaju u druge funkcije. Dekorateri nam omogućavaju da pišemo čistiji kod i delimo funkcionalnosti na efikasan način. Ovaj članak je detaljan vodič ne samo o upotrebi, već i o kreiranju sopstvenih dekoratera.
Neophodno predznanje
Tema dekoratera u Python-u zahteva određeno osnovno znanje. U nastavku su navedeni koncepti sa kojima bi trebalo da budete upoznati kako biste lakše razumeli ovaj vodič. Takođe su priloženi linkovi ka resursima gde možete obnoviti te koncepte ukoliko je potrebno.
Osnove Python-a
Ova tema spada u srednje naprednu kategoriju. Zato, pre nego što počnete sa učenjem, trebalo bi da budete upoznati sa osnovama Python-a, kao što su tipovi podataka, funkcije, objekti i klase. Takođe je poželjno razumevanje koncepta objektno orijentisanog programiranja, uključujući getere, setere i konstruktore. Ako niste upoznati sa Python-om, evo nekoliko resursa za početak.
Funkcije kao objekti prvog reda
Pored osnovnog Python-a, važno je da razumete i napredniji koncept. U Python-u su funkcije objekti, baš kao i int ili string. S obzirom na to da su objekti, možete ih koristiti na različite načine:
- Možete proslediti funkciju kao argument drugoj funkciji, baš kao što biste prosledili string ili int.
- Funkcije mogu vraćati druge funkcije, slično vraćanju string ili int vrednosti.
- Funkcije se mogu čuvati u promenljivim.
Zapravo, jedina razlika između funkcionalnih objekata i ostalih objekata je u tome što funkcionalni objekti sadrže magičnu metodu __call__()
.
Nadam se da ste u ovom trenutku zadovoljni sa potrebnim predznanjem. Sada možemo da pređemo na glavnu temu.
Šta je Python dekorater?
Python dekorater je funkcija koja prima drugu funkciju kao argument i vraća izmenjenu verziju te funkcije. Drugim rečima, funkcija foo
je dekorater ako kao argument uzima funkciju bar
i vraća drugu funkciju baz
.
Funkcija baz
je modifikacija funkcije bar
, gde unutar tela baz
postoji poziv funkcije bar
. Međutim, pre i posle poziva bar
, funkcija baz
može izvršiti bilo koju operaciju. Da bi ovo bilo jasnije, sledi primer koda:
# Foo je dekorater, prima drugu funkciju, bar, kao argument def foo(bar): # Kreiramo baz, modifikovanu verziju bar # baz će pozvati bar, ali može izvršiti bilo šta pre i posle poziva funkcije def baz(): # Pre poziva bar, štampamo nešto print("Nešto") # Zatim pokrećemo bar pozivom funkcije bar() # Zatim štampamo nešto drugo nakon pokretanja bar print("Nešto drugo") # Na kraju, foo vraća baz, modifikovanu verziju bar return baz
Kako kreirati dekorater u Python-u?
Kako bih ilustrovao kreiranje i upotrebu dekoratera u Python-u, koristiću jednostavan primer. Kreiraćemo funkciju dekoratera logger
, koja će beležiti ime funkcije koju ukrašava svaki put kada se ta funkcija izvrši.
Za početak, kreiramo funkciju dekoratera. Dekorater prima funkciju kao argument. func
je funkcija koju ukrašavamo.
def create_logger(func): # Telo funkcije ide ovde
Unutar funkcije dekoratera, kreiraćemo našu modifikovanu funkciju koja će zabeležiti ime func
pre pokretanja same func
.
# Unutar create_logger def modified_func(): print("Poziva se: ", func.__name__) func()
Zatim, funkcija create_logger
će vratiti izmenjenu funkciju. Kao rezultat, cela naša funkcija create_logger
će izgledati ovako:
def create_logger(func): def modified_func(): print("Poziva se: ", func.__name__) func() return modified_func
Završili smo sa kreiranjem dekoratera. Funkcija create_logger
je jednostavan primer funkcije dekoratera. Ona prima func
, funkciju koju ukrašavamo, i vraća drugu funkciju, modified_func
. modified_func
prvo beleži ime func
, a zatim pokreće samu func
.
Kako koristiti dekoratere u Python-u
Da bismo koristili naš dekorater, koristimo @
sintaksu na sledeći način:
@create_logger def say_hello(): print("Zdravo, Svete!")
Sada možemo pozvati say_hello()
u našem skriptu, i izlaz bi trebao biti sledeći:
Poziva se: say_hello "Zdravo, Svete!"
Šta zapravo radi @create_logger
? To je primena dekoratera na našu say_hello
funkciju. Da bismo bolje razumeli šta se dešava, kod neposredno ispod ovog pasusa bi postigao isti rezultat kao i stavljanje @create_logger
ispred say_hello
.
def say_hello(): print("Zdravo, Svete!") say_hello = create_logger(say_hello)
Drugim rečima, jedan od načina korišćenja dekoratera u Python-u je da eksplicitno pozovete dekorater prosleđujući funkciju, kao što smo uradili u gornjem kodu. Drugi, sažetiji način, je upotreba @
sintakse.
U ovom odeljku smo opisali kako se kreiraju Python dekorateri.
Malo komplikovaniji primeri
Gornji primer je bio jednostavan. Postoje malo složeniji slučajevi, kao što je kada funkcija koju ukrašavamo prima argumente. Druga složenija situacija je kada želite da ukrasite celu klasu. Ovde ću objasniti obe situacije.
Kada funkcija prima argumente
Kada funkcija koju ukrašavate prima argumente, modifikovana funkcija mora da primi argumente i prosledi ih kada na kraju pozove neizmenjenu funkciju. Ako ovo zvuči zbunjujuće, dozvolite da objasnim koristeći foo-bar
terminologiju.
Podsetimo se da je foo
dekoraterska funkcija, bar
je funkcija koju ukrašavamo, a baz
je ukrašena verzija bar
. U tom slučaju, bar
će primati argumente i prosleđivati ih baz
prilikom poziva. Primer koda koji učvršćuje koncept:
def foo(bar): def baz(*args, **kwargs): # Ovde možete nešto uraditi ___ # Zatim pozivamo bar, prosleđujući args i kwargs bar(*args, **kwargs) # Ovde takođe možete nešto uraditi ___ return baz
Ako *args
i **kwargs
izgledaju nepoznato, oni su pokazivači na pozicione argumente i ključne reči, respektivno.
Važno je napomenuti da baz
ima pristup argumentima i stoga može izvršiti proveru argumenata pre nego što pozove bar
.
Primer bi bio funkcija dekoratera, ensure_string
, koja osigurava da je argument prosleđen funkciji koju ukrašava string. Implementirali bismo je ovako:
def ensure_string(func): def decorated_func(text): if type(text) is not str: raise TypeError('Argument funkcije ' + func.__name__ + ' mora biti string.') else: func(text) return decorated_func
Mogli bismo ukrasiti funkciju say_hello
ovako:
@ensure_string def say_hello(name): print('Zdravo', name)
Zatim bismo mogli testirati kod koristeći ovo:
say_hello('John') # Trebalo bi da radi bez problema say_hello(3) # Trebalo bi da baci izuzetak
I trebalo bi da dobijemo sledeći izlaz:
Zdravo John Traceback (most recent call last): File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, insay_hello(3) # trebalo bi da baci izuzetak File "/home/anesu/Documents/python-tutorial/./decorators.py", line 7, in decorated_func raise TypeError('Argument funkcije + func.__name__ + mora biti string.') TypeError: Argument funkcije say_hello mora biti string. $0
Kao što se i očekivalo, program je uspeo da odštampa ‘Zdravo John’ jer je ‘John’ string. Izbacio je izuzetak kada je pokušao da odštampa ‘Zdravo 3’ jer ‘3’ nije bio string. Dekorater ensure_string
se može koristiti za validaciju argumenata bilo koje funkcije koja zahteva string.
Dekorisanje klase
Osim što ukrašavamo funkcije, možemo ukrasiti i klase. Kada dodate dekorater klasi, dekorišani metod zamenjuje metod konstruktora/inicijatora klase (__init__
).
Vraćajući se na foo-bar
primer, pretpostavimo da je foo
naš dekorater, a Bar
klasa koju ukrašavamo. Tada će foo
ukrasiti Bar.__init__
. Ovo će biti korisno ako želimo da uradimo nešto pre nego što se kreiraju objekti tipa Bar
.
To znači da sledeći kod:
def foo(func): def new_func(*args, **kwargs): print('Izvršava se nešto pre instanciranja') func(*args, **kwargs) return new_func @foo class Bar: def __init__(self): print("U inicijatoru")
Je ekvivalentan sledećem:
def foo(func): def new_func(*args, **kwargs): print('Izvršava se nešto pre instanciranja') func(*args, **kwargs) return new_func class Bar: def __init__(self): print("U inicijatoru") Bar.__init__ = foo(Bar.__init__)
U stvari, instanciranje objekta klase Bar
, definisanog korišćenjem bilo koje od dve metode, trebalo bi da pruži isti izlaz:
Izvršava se nešto pre instanciranja U inicijatoru
Primeri dekoratera u Python-u
Iako možete definisati svoje dekoratere, postoje neki koji su već ugrađeni u Python. Evo nekih od uobičajenih dekoratera na koje možete naići:
@staticmethod
@staticmethod
se koristi za klasu da naznači da je metod koji ukrašava statički metod. Statički metodi su metodi koji se mogu pokrenuti bez potrebe za instanciranjem klase. U sledećem primeru koda kreiramo klasu Dog
sa statičkim metodom bark
.
class Dog: @staticmethod def bark(): print('Vau, vau!')
Sada se metodu bark
može pristupiti ovako:
Dog.bark()
A pokretanje koda bi proizvelo sledeći izlaz:
Vau, vau!
Kao što sam pomenuo u odeljku „Kako se koriste dekorateri“, dekorateri se mogu koristiti na dva načina. Sintaksa @
je sažetija, dok je drugi metod da pozovete funkciju dekoratera, prosleđujući funkciju koju želimo da ukrasimo kao argument. To znači da gornji kod postiže istu stvar kao kod ispod:
class Dog: def bark(): print('Vau, vau!') Dog.bark = staticmethod(Dog.bark)
I dalje možemo koristiti metod bark
na isti način:
Dog.bark()
I to bi proizvelo isti izlaz:
Vau, vau!
Kao što vidite, prvi metod je čistiji i očiglednije je da je funkcija statička funkcija pre nego što ste uopšte počeli da čitate kod. Zato ću za preostale primere koristiti prvi metod. Ali zapamtite da je drugi metod alternativa.
@classmethod
Ovaj dekorater se koristi da označi da je metod koji ukrašava metod klase. Metodi klase su slični statičkim metodima po tome što oba ne zahtevaju da se klasa instancira pre nego što se mogu pozvati.
Međutim, glavna razlika je u tome što metodi klase imaju pristup atributima klase, dok statički metodi nemaju. To je zato što Python automatski prosleđuje klasu kao prvi argument metodu klase kad god se ona pozove. Da bismo kreirali metod klase u Python-u, možemo koristiti dekorater classmethod
.
class Dog: @classmethod def what_are_you(cls): print("Ja sam " + cls.__name__ + "!")
Da bismo pokrenuli kod, jednostavno pozivamo metod bez instanciranja klase:
Dog.what_are_you()
A izlaz je:
Ja sam Dog!
@property
Dekorater property
se koristi za označavanje metoda kao parametra svojstva. Vraćajući se na naš primer psa, kreiraćemo metod koji uzima ime psa.
class Dog: # Kreiramo konstruktor koji prima ime psa def __init__(self, name): # Kreiramo privatno svojstvo ime # Dupla podvlaka čini atribut privatnim self.__name = name @property def name(self): return self.__name
Sada možemo pristupiti imenu psa kao normalnom svojstvu:
# Kreiranje instance klase foo = Dog('foo') # Pristupanje svojstvu name print("Ime psa je:", foo.name)
A rezultat pokretanja koda bi bio:
Ime psa je: foo
@property.setter
Dekorater property.setter
se koristi za kreiranje metoda setera za naša svojstva. Da biste koristili dekorater @property.setter
, zamenite svojstvo imenom svojstva za koje kreirate seter. Na primer, ako kreirate seter za metod za svojstvo foo
, vaš dekorater će biti @foo.setter
. Evo primera psa za ilustraciju:
class Dog: # Kreiranje konstruktora koji prima ime psa def __init__(self, name): # Kreiramo privatno svojstvo ime # Dupla podvlaka čini atribut privatnim self.__name = name @property def name(self): return self.__name # Kreiranje setera za svojstvo name @name.setter def name(self, new_name): self.__name = new_name
Da bismo testirali seter, možemo koristiti sledeći kod:
# Kreiranje novog psa foo = Dog('foo') # Promena imena psa foo.name="bar" # Štampanje novog imena psa na ekran print("Novo ime psa je:", foo.name)
Pokretanje koda će proizvesti sledeći izlaz:
Novo ime psa je: bar
Značaj dekoratera u Python-u
Sada kada smo objasnili šta su dekorateri i videli neke primere, možemo da razgovaramo o tome zašto su dekorateri važni u Python-u. Dekorateri su važni iz više razloga, od kojih sam neke naveo u nastavku:
- Omogućavaju ponovnu upotrebu koda: U gornjem primeru beleženja, mogli smo da koristimo
@create_logger
na bilo kojoj funkciji koju želimo. Ovo nam omogućava da dodamo funkcionalnost beleženja svim našim funkcijama bez ručnog pisanja za svaku funkciju. - Omogućavaju pisanje modularnog koda: Vraćajući se ponovo na primer beleženja, sa dekoraterima možete odvojiti osnovnu funkciju, u ovom slučaju
say_hello
, od druge funkcionalnosti koja vam je potrebna, u ovom slučaju beleženja. - Poboljšavaju framework-e i biblioteke: Dekorateri se u velikoj meri koriste u Python framework-ima i bibliotekama kako bi obezbedili dodatnu funkcionalnost. Na primer, u web framework-ima kao što su Flask ili Django, dekorateri se koriste za definisanje ruta, rukovanje autentifikacijom ili primenu middleware-a na određene view-e.
Završne reči
Dekorateri su neverovatno korisni; možete ih koristiti za proširenje funkcija bez promene njihove funkcionalnosti. Ovo je korisno kada želite da merite performanse funkcija, beležite svaki put kada se funkcija pozove, potvrdite argumente pre pozivanja funkcije ili proverite dozvole pre pokretanja funkcije. Jednom kada razumete dekoratere, moći ćete da pišete kod na čistiji način.
Sledeće, možda ćete želeti da pročitate naše članke o tuple-ovima i korišćenju cURL-a u Python-u.