Explicat (cu exemple și cazuri de utilizare)

Decoratorii Python sunt o construcție incredibil de utilă în Python. Folosind decoratorii în Python, putem modifica comportamentul unei funcții prin includerea acesteia în altă funcție. Decoratorii ne permit să scriem un cod mai curat și să partajăm funcționalități. Acest articol este un tutorial nu numai despre cum să folosiți decoratorii, ci și cum să le creați.

Cunoștințe prealabile

Tema decoratorilor în Python necesită anumite cunoștințe de bază. Mai jos, am enumerat câteva concepte cu care ar trebui să fiți deja familiarizați pentru a înțelege acest tutorial. Am, de asemenea, legat de resurse de unde puteți periați conceptele dacă este necesar.

Python de bază

Acest subiect este un subiect mai intermediar/avansat. Ca rezultat, înainte de a încerca să învățați, ar trebui să fiți deja familiarizați cu elementele de bază ale Python, cum ar fi tipurile de date, funcțiile, obiectele și clasele.

De asemenea, ar trebui să înțelegeți câteva concepte orientate pe obiecte, cum ar fi getters, setters și constructori. Dacă nu sunteți familiarizat cu limbajul de programare Python, iată câteva resurse pentru a începe.

Funcțiile sunt cetățeni de primă clasă

Pe lângă Python de bază, ar trebui să fiți conștienți și de acest concept mai avansat în Python. Funcțiile și aproape orice altceva în Python sunt obiecte precum int sau string. Pentru că sunt obiecte, puteți face câteva lucruri cu ele și anume:

  • Puteți trece o funcție ca argument unei alte funcții în același mod în care treceți un șir de caractere sau int ca argument de funcție.
  • Funcțiile pot fi returnate și de alte funcții, așa cum ați returna alte valori șir sau int.
  • Funcțiile pot fi stocate în variabile

De fapt, singura diferență dintre obiectele funcționale și alte obiecte este că obiectele funcționale conțin metoda magică __call__().

Sperăm că, în acest moment, vă simțiți confortabil cu cunoștințele prealabile. Putem începe să discutăm despre subiectul principal.

Ce este un Decorator Python?

Un decorator Python este pur și simplu o funcție care preia o funcție ca argument și returnează o versiune modificată a funcției care a fost transmisă. Cu alte cuvinte, funcția foo este un decorator dacă ia, ca argument, bara de funcții și returnează o altă funcție baz.

Funcția baz este o modificare a barei în sensul că în corpul bazului, există un apel la bara funcției. Cu toate acestea, înainte și după apelul la bar, baz poate face orice. A fost o gură; iată un cod pentru a ilustra situația:

# 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

Cum se creează un decorator în Python?

Pentru a ilustra modul în care decoratorii sunt creați și utilizați în Python, voi ilustra acest lucru cu un exemplu simplu. În acest exemplu, vom crea o funcție de decorare logger care va înregistra numele funcției pe care o decorează de fiecare dată când acea funcție este rulată.

  Un ghid pentru deviația configurației și cum să o preveniți

Pentru a începe, am creat funcția de decorator. Decoratorul ia funcția ca argument. func este funcția pe care o decoram.

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

În interiorul funcției de decorator, vom crea funcția noastră modificată care va înregistra numele func înainte de a rula func.

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

Apoi, funcția create_logger va returna funcția modificată. Ca rezultat, întreaga noastră funcție create_logger va arăta astfel:

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

    return modified_function

Am terminat de creat decoratorul. Funcția create_logger este un exemplu simplu de funcție de decorator. Preia func, care este funcția pe care o decoram, și returnează o altă funcție, modified_func. modified_func înregistrează mai întâi numele func, înainte de a rula func.

Cum să folosiți decoratorii în Python

Pentru a folosi decoratorul nostru, folosim sintaxa @ astfel:

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

Acum putem apela say_hello() în scriptul nostru, iar rezultatul ar trebui să fie următorul text:

Calling:  say_hello
"Hello, World"

Dar ce face @create_logger? Ei bine, se aplică decoratorul la funcția noastră say_hello. Pentru a înțelege mai bine ce se întâmplă, codul imediat sub acest paragraf ar obține același rezultat ca și punerea @create_logger înainte de say_hello.

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

say_hello = create_logger(say_hello)

Cu alte cuvinte, o modalitate de a folosi decoratorii în Python este să apelați în mod explicit decoratorul care transmite funcția așa cum am făcut în codul de mai sus. Cealaltă modalitate și mai concisă este să folosești sintaxa @.

În această secțiune, am abordat cum să creați decoratori Python.

Exemple ceva mai complicate

Exemplul de mai sus a fost un caz simplu. Există exemple puțin mai complexe, cum ar fi atunci când funcția pe care o decoram preia argumente. O altă situație mai complicată este atunci când vrei să decorezi o clasă întreagă. Voi acoperi ambele situații aici.

Când funcția preia argumente

Când funcția pe care o decorați preia argumente, funcția modificată ar trebui să primească argumentele și să le transmită atunci când în cele din urmă face apel la funcția nemodificată. Dacă sună confuz, permiteți-mi să vă explic în termeni foo-bar.

Amintiți-vă că foo este funcția de decorator, bar este funcția pe care o decoram și baz este barul decorat. În acest caz, bar va prelua argumentele și le va transmite lui baz în timpul apelului către baz. Iată un exemplu de cod pentru a consolida conceptul:

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

Dacă *args și **kwargs par nefamiliare; sunt pur și simplu indicatoare către argumentele poziționale și, respectiv, cheie.

Este important de reținut că baz are acces la argumente și, prin urmare, poate efectua o anumită validare a argumentelor înainte de a apela bar.

Un exemplu ar fi dacă am avea o funcție decorator, assure_string care ar asigura că argumentul transmis unei funcții pe care o decorează este un șir; l-am implementa astfel:

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

Am putea decora funcția say_hello astfel:

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

Apoi am putea testa codul folosind asta:

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

Și ar trebui să producă următorul rezultat:

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

După cum era de așteptat, scenariul a reușit să imprime „Hello John” deoarece „John” este un șir. A lansat o excepție când a încercat să tipăriți „Hello 3” deoarece „3” nu era un șir. Decoratorul assure_string poate fi folosit pentru a valida argumentele oricărei funcții care necesită un șir.

  5 moduri de a face mai mulți bani în „Stardew Valley”

Decorarea unei clase

Pe lângă doar funcțiile de decorare, putem și decora cursuri. Când adăugați un decorator la o clasă, metoda decorată înlocuiește metoda constructor/inițiator a clasei (__init__).

Revenind la foo-bar, să presupunem că foo este decoratorul nostru și Bar este clasa pe care o decoram, apoi foo va decora Bar.__init__. Acest lucru va fi util dacă vrem să facem ceva înainte ca obiectele de tip Bar să fie instanțiate.

Aceasta înseamnă că următorul cod

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

Este echivalent cu

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

De fapt, instanțierea unui obiect din clasa Bar, definit folosind oricare dintre cele două metode, ar trebui să vă ofere aceeași ieșire:

Doing some stuff before instantiation
In initiator

Exemplu de decoratori în Python

Deși vă puteți defini propriii decoratori, există unii care sunt deja încorporați în Python. Iată câțiva dintre decoratorii obișnuiți pe care îi puteți întâlni în Python:

@staticmethod

Metoda statică este utilizată pe o clasă pentru a indica faptul că metoda pe care o decorează este o metodă statică. Metodele statice sunt metode care pot rula fără a fi nevoie de instanțierea clasei. În următorul exemplu de cod, creăm o clasă Dog cu o metodă statică bark.

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

Acum metoda scoarței poate fi accesată astfel:

Dog.bark()

Și rularea codului ar produce următoarea ieșire:

Woof, woof!

După cum am menționat în secțiunea Cum să folosiți Decoratorii, decoratorii pot fi utilizați în două moduri. Sintaxa @ fiind mai concisă fiind una dintre cele două. Cealaltă metodă este să apelăm funcția decorator, trecând funcția pe care dorim să o decoram ca argument. Adică, codul de mai sus realizează același lucru ca și codul de mai jos:

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

Dog.bark = staticmethod(Dog.bark)

Și putem folosi în continuare metoda scoarței în același mod

Dog.bark()

Și ar produce aceeași ieșire

Woof, woof!

După cum puteți vedea, prima metodă este mai curată și este mai evident că funcția este o funcție statică chiar înainte de a începe să citiți codul. Ca urmare, pentru exemplele rămase, voi folosi prima metodă. Dar amintiți-vă că a doua metodă este o alternativă.

@classmethod

Acest decorator este folosit pentru a indica faptul că metoda pe care o decorează este o metodă de clasă. Metodele de clasă sunt similare cu metodele statice prin faptul că ambele nu necesită ca clasa să fie instanțiată înainte de a putea fi apelate.

  13 cele mai populare dispozitive IoT pentru acasă și serviciu

Cu toate acestea, principala diferență este că metodele de clasă au acces la atributele de clasă, în timp ce metodele statice nu. Acest lucru se datorează faptului că Python transmite automat clasa ca prim argument unei metode de clasă ori de câte ori este apelată. Pentru a crea o metodă de clasă în Python, putem folosi decoratorul classmethod.

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

Pentru a rula codul, apelăm pur și simplu metoda fără a instanția clasa:

Dog.what_are_you()

Și rezultatul este:

I am a Dog!

@proprietate

Decoratorul de proprietăți este folosit pentru a eticheta o metodă ca stabilitor de proprietăți. Revenind la exemplul nostru de câine, să creăm o metodă care preia numele câinelui.

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

Acum putem accesa numele câinelui ca pe o proprietate normală,

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

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

Iar rezultatul rulării codului ar fi

The dog's name is: foo

@property.setter

Decoratorul property.setter este folosit pentru a crea o metodă de setter pentru proprietățile noastre. Pentru a utiliza decoratorul @property.setter, înlocuiți proprietatea cu numele proprietății, pentru care creați un setter. De exemplu, dacă creați un setter pentru metoda pentru proprietatea foo, decoratorul dvs. va fi @foo.setter. Iată un exemplu de câine pentru a ilustra:

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

Pentru a testa setter-ul, putem folosi următorul cod:

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

Rularea codului va produce următoarea ieșire:

The dogs's new name is: bar

Importanța decoratorilor în Python

Acum că am acoperit ce sunt decoratorii și ați văzut câteva exemple de decoratori, putem discuta de ce decoratorii sunt importanți în Python. Decoratorii sunt importanți din mai multe motive. Unele dintre ele, le-am enumerat mai jos:

  • Acestea permit reutilizarea codului: în exemplul de înregistrare dat mai sus, am putea folosi @create_logger pentru orice funcție dorim. Acest lucru ne permite să adăugăm funcționalitate de înregistrare la toate funcțiile noastre fără a o scrie manual pentru fiecare funcție.
  • Ele vă permit să scrieți cod modular: Din nou, revenind la exemplul de logare, cu decoratori, puteți separa funcția de bază, în acest caz say_hello de cealaltă funcționalitate de care aveți nevoie, în acest caz, de logare.
  • Acestea îmbunătățesc cadrele și bibliotecile: Decoratorii sunt utilizați pe scară largă în cadrele și bibliotecile Python pentru a oferi funcționalități suplimentare. De exemplu, în cadrele web precum Flask sau Django, decoratorii sunt utilizați pentru definirea rutelor, gestionarea autentificării sau aplicarea middleware-ului la anumite vizualizări.

Cuvinte finale

Decoratorii sunt incredibil de folositori; le puteți folosi pentru a extinde funcții fără a le modifica funcționalitatea. Acest lucru este util atunci când doriți să cronometrați performanța funcțiilor, să vă înregistrați ori de câte ori este apelată o funcție, să validați argumente înainte de a apela o funcție sau să verificați permisiunile înainte ca o funcție să fie rulată. Odată ce înțelegi decoratorii, vei putea scrie codul într-un mod mai curat.

Apoi, poate doriți să citiți articolele noastre despre tupluri și utilizarea cURL în Python.