Objaśnienie (z przykładami i przypadkami użycia)

Dekoratory Pythona to niezwykle użyteczna konstrukcja w Pythonie. Korzystając z dekoratorów w Pythonie, możemy modyfikować zachowanie funkcji, owijając ją wewnątrz innej funkcji. Dekoratory umożliwiają nam pisanie czystszego kodu i udostępnianie funkcjonalności. Ten artykuł jest samouczkiem dotyczącym nie tylko używania dekoratorów, ale także sposobu ich tworzenia.

Wymagana wiedza

Temat dekoratorów w Pythonie wymaga pewnej wiedzy podstawowej. Poniżej wymieniłem kilka pojęć, które powinieneś już znać, aby zrozumieć ten samouczek. Połączyłem również zasoby, w których możesz odświeżyć koncepcje, jeśli zajdzie taka potrzeba.

Podstawowy Python

Ten temat jest bardziej średniozaawansowany/zaawansowany. W rezultacie przed przystąpieniem do nauki powinieneś już znać podstawy Pythona, takie jak typy danych, funkcje, obiekty i klasy.

Powinieneś także zrozumieć niektóre koncepcje zorientowane obiektowo, takie jak metody pobierające, ustawiające i konstruktory. Jeśli nie znasz języka programowania Python, oto kilka zasobów, które pomogą Ci zacząć.

Funkcje są obywatelami pierwszej kategorii

Oprócz podstawowego języka Python powinieneś również znać tę bardziej zaawansowaną koncepcję w języku Python. Funkcje i prawie wszystko inne w Pythonie są obiektami takimi jak int lub string. Ponieważ są przedmiotami, możesz zrobić z nimi kilka rzeczy, a mianowicie:

  • Możesz przekazać funkcję jako argument do innej funkcji w taki sam sposób, w jaki przekazujesz ciąg znaków lub int jako argument funkcji.
  • Funkcje mogą być również zwracane przez inne funkcje, tak jak w przypadku zwracania innych wartości typu string lub int.
  • Funkcje mogą być przechowywane w zmiennych

W rzeczywistości jedyną różnicą między obiektami funkcjonalnymi a innymi obiektami jest to, że obiekty funkcjonalne zawierają magiczną metodę __call__().

Mamy nadzieję, że w tym momencie jesteś zadowolony z wymaganej wiedzy. Możemy zacząć omawiać główny temat.

Co to jest dekorator Pythona?

Dekorator Pythona to po prostu funkcja, która przyjmuje funkcję jako argument i zwraca zmodyfikowaną wersję przekazanej funkcji. Innymi słowy, funkcja foo jest dekoratorem, jeśli przyjmuje jako argument pasek funkcji i zwraca kolejną funkcję baz.

Funkcja baz jest modyfikacją paska w tym sensie, że w ciele bazy znajduje się wywołanie paska funkcji. Jednak przed i po wezwaniu do baru baz może zrobić wszystko. To było pełne kęsów; oto kod ilustrujący sytuację:

# 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

Jak stworzyć dekoratora w Pythonie?

Aby zilustrować sposób tworzenia i używania dekoratorów w Pythonie, zilustruję to prostym przykładem. W tym przykładzie utworzymy funkcję dekoratora loggera, która będzie rejestrować nazwę dekorowanej funkcji za każdym razem, gdy ta funkcja zostanie uruchomiona.

Aby rozpocząć, stworzyliśmy funkcję dekoratora. Dekorator przyjmuje func jako argument. func to funkcja, którą dekorujemy.

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

Wewnątrz funkcji dekoratora stworzymy naszą zmodyfikowaną funkcję, która zapisze nazwę func przed uruchomieniem func.

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

Następnie funkcja create_logger zwróci zmodyfikowaną funkcję. W rezultacie cała nasza funkcja create_logger będzie wyglądać następująco:

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

    return modified_function

Zakończyliśmy tworzenie dekoratora. Funkcja create_logger jest prostym przykładem funkcji dekoratora. Przyjmuje func, czyli funkcję, którą dekorujemy, i zwraca inną funkcję,zmodyfikowaną_funkcję. Modified_func najpierw rejestruje nazwę func przed uruchomieniem func.

Jak używać dekoratorów w Pythonie

Aby użyć naszego dekoratora, używamy składni @ w następujący sposób:

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

Teraz możemy wywołać say_hello() w naszym skrypcie, a wynikiem powinien być następujący tekst:

Calling:  say_hello
"Hello, World"

Ale co robi @create_logger? Cóż, stosuje dekorator do naszej funkcji say_hello. Aby lepiej zrozumieć, co się dzieje, kod bezpośrednio pod tym akapitem dałby taki sam wynik, jak umieszczenie @create_logger przed say_hello.

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

say_hello = create_logger(say_hello)

Innymi słowy, jednym ze sposobów użycia dekoratorów w Pythonie jest jawne wywołanie dekoratora przekazującego funkcję, tak jak zrobiliśmy to w powyższym kodzie. Innym i bardziej zwięzłym sposobem jest użycie składni @.

W tej sekcji omówiliśmy, jak tworzyć dekoratory Pythona.

Nieco bardziej skomplikowane przykłady

Powyższy przykład był prostym przypadkiem. Istnieją nieco bardziej złożone przykłady, na przykład gdy funkcja, którą dekorujemy, przyjmuje argumenty. Inną bardziej skomplikowaną sytuacją jest sytuacja, gdy chcesz udekorować całą klasę. Omówię tutaj obie te sytuacje.

Gdy funkcja przyjmuje argumenty

Gdy funkcja, którą dekorujesz, przyjmuje argumenty, zmodyfikowana funkcja powinna je otrzymać i przekazać, gdy ostatecznie wywoła funkcję niezmodyfikowaną. Jeśli brzmi to myląco, pozwól, że wyjaśnię w kategoriach foo-bar.

Przypomnijmy, że foo to funkcja dekoratora, bar to funkcja, którą dekorujemy, a baz to udekorowany pasek. W takim przypadku bar przyjmie argumenty i przekaże je bazowi podczas wywołania baz. Oto przykład kodu, aby utrwalić koncepcję:

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

Jeśli *args i **kwargs wyglądają nieznajomo; są to po prostu wskaźniki odpowiednio do argumentów pozycyjnych i słów kluczowych.

Należy zauważyć, że baz ma dostęp do argumentów i dlatego przed wywołaniem bar może wykonać pewne sprawdzanie poprawności argumentów.

Przykładem byłoby, gdybyśmy mieli funkcję dekoratora, sure_string, która zapewniłaby, że argument przekazany do funkcji, którą dekoruje, jest łańcuchem; zaimplementowalibyśmy to tak:

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

Możemy ozdobić funkcję say_hello w następujący sposób:

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

Następnie moglibyśmy przetestować kod za pomocą tego:

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

I powinno dać następujący wynik:

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

Zgodnie z oczekiwaniami skrypt zdołał wyświetlić „Hello John”, ponieważ „John” to ciąg znaków. Zgłosił wyjątek podczas próby wydrukowania „Hello 3”, ponieważ „3” nie było ciągiem znaków. Do sprawdzania poprawności argumentów dowolnej funkcji, która wymaga ciągu znaków, można użyć dekoratora sure_string.

Dekorowanie klasy

Oprócz samych funkcji dekoracyjnych możemy również ozdobić klasy. Kiedy dodajesz dekorator do klasy, dekorowana metoda zastępuje metodę konstruktora/inicjatora klasy (__init__).

Wracając do foo-bar, załóżmy, że foo jest naszym dekoratorem, a Bar jest klasą, którą dekorujemy, wtedy foo ozdobi Bar.__init__. Będzie to przydatne, jeśli chcemy zrobić cokolwiek przed utworzeniem instancji obiektów typu Bar.

Oznacza to, że poniższy kod

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

Jest równa

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

W rzeczywistości utworzenie instancji obiektu klasy Bar, zdefiniowanej przy użyciu jednej z dwóch metod, powinno dać taki sam wynik:

Doing some stuff before instantiation
In initiator

Przykładowe dekoratory w Pythonie

Chociaż możesz zdefiniować własne dekoratory, niektóre z nich są już wbudowane w Pythona. Oto niektóre z typowych dekoratorów, które możesz napotkać w Pythonie:

@metoda statyczna

Metoda statyczna jest używana w klasie, aby wskazać, że metoda, którą dekoruje, jest metodą statyczną. Metody statyczne to metody, które można uruchamiać bez konieczności tworzenia instancji klasy. W poniższym przykładzie kodu tworzymy klasę Dog z metodą statyczną bark.

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

Teraz dostęp do metody kory można uzyskać w następujący sposób:

Dog.bark()

A uruchomienie kodu dałoby następujące dane wyjściowe:

Woof, woof!

Jak wspomniałem w sekcji Jak używać dekoratorów, dekoratorów można używać na dwa sposoby. Składnia @ jest bardziej zwięzła i jest jedną z dwóch. Inną metodą jest wywołanie funkcji dekoratora, przekazując jako argument funkcję, którą chcemy ozdobić. Oznacza to, że powyższy kod osiąga to samo, co poniższy kod:

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

Dog.bark = staticmethod(Dog.bark)

I nadal możemy używać metody kory w ten sam sposób

Dog.bark()

I dałoby to samo wyjście

Woof, woof!

Jak widać, pierwsza metoda jest czystsza i bardziej oczywiste jest, że funkcja jest funkcją statyczną, zanim jeszcze zaczniesz czytać kod. W rezultacie dla pozostałych przykładów użyję pierwszej metody. Ale pamiętaj, że druga metoda jest alternatywą.

@metodaklasy

Ten dekorator służy do wskazania, że ​​metoda, którą dekoruje, jest metodą klasową. Metody klasowe są podobne do metod statycznych, ponieważ obie nie wymagają utworzenia instancji klasy przed ich wywołaniem.

Jednak główna różnica polega na tym, że metody klasowe mają dostęp do atrybutów klas, podczas gdy metody statyczne nie. Dzieje się tak, ponieważ Python automatycznie przekazuje klasę jako pierwszy argument do metody klasowej za każdym razem, gdy jest ona wywoływana. Aby utworzyć metodę klasową w Pythonie, możemy użyć dekoratora metody klasowej.

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

Aby uruchomić kod, po prostu wywołujemy metodę bez tworzenia instancji klasy:

Dog.what_are_you()

A wyjście to:

I am a Dog!

@nieruchomość

Dekorator właściwości służy do oznaczania metody jako ustawiającej właściwości. Wracając do naszego przykładu Psa, stwórzmy metodę, która pobiera nazwę Psa.

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

Teraz możemy uzyskać dostęp do imienia psa jak do normalnej właściwości,

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

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

A wynikiem uruchomienia kodu byłoby

The dog's name is: foo

@property.setter

Dekorator property.setter służy do tworzenia metody ustawiającej dla naszych właściwości. Aby użyć dekoratora @property.setter, zamieniasz właściwość na nazwę właściwości, dla której tworzysz setter. Na przykład, jeśli tworzysz setter dla metody dla właściwości foo, twoim dekoratorem będzie @foo.setter. Oto przykład psa dla zilustrowania:

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

Aby przetestować seter, możemy użyć następującego kodu:

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

Uruchomienie kodu spowoduje wyświetlenie następującego wyniku:

The dogs's new name is: bar

Znaczenie dekoratorów w Pythonie

Teraz, gdy omówiliśmy, czym są dekoratory i widziałeś kilka przykładów dekoratorów, możemy omówić, dlaczego dekoratory są ważne w Pythonie. Dekoratory są ważne z kilku powodów. Niektóre z nich wymieniłem poniżej:

  • Umożliwiają ponowne użycie kodu: w powyższym przykładzie rejestrowania moglibyśmy użyć @create_logger w dowolnej funkcji. Dzięki temu możemy dodać funkcję rejestrowania do wszystkich naszych funkcji bez konieczności ręcznego zapisywania jej dla każdej funkcji.
  • Pozwalają na pisanie kodu modułowego: ponownie, wracając do przykładu logowania, za pomocą dekoratorów możesz oddzielić podstawową funkcję, w tym przypadku say_hello, od innej potrzebnej funkcjonalności, w tym przypadku logowania.
  • Ulepszają frameworki i biblioteki: Dekoratory są szeroko stosowane w frameworkach i bibliotekach Pythona w celu zapewnienia dodatkowej funkcjonalności. Na przykład w frameworkach internetowych, takich jak Flask lub Django, dekoratory są używane do definiowania tras, obsługi uwierzytelniania lub stosowania oprogramowania pośredniego do określonych widoków.

Ostatnie słowa

Dekoratory są niezwykle przydatne; można ich używać do rozszerzania funkcji bez zmiany ich funkcjonalności. Jest to przydatne, gdy chcesz mierzyć czas działania funkcji, rejestrować każde wywołanie funkcji, sprawdzać poprawność argumentów przed wywołaniem funkcji lub weryfikować uprawnienia przed uruchomieniem funkcji. Gdy zrozumiesz dekoratory, będziesz mógł pisać kod w czystszy sposób.

Następnie możesz przeczytać nasze artykuły na temat krotek i używania cURL w Pythonie.