Vysvětleno (s příklady a případy použití)

Dekorátory Pythonu jsou neuvěřitelně užitečnou konstrukcí v Pythonu. Pomocí dekorátorů v Pythonu můžeme upravit chování funkce tím, že ji zabalíme do jiné funkce. Dekorátory nám umožňují psát čistší kód a sdílet funkce. Tento článek je návodem, jak dekorátory nejen používat, ale jak je vytvářet.

Předpoklad znalost

Téma dekoratérů v Pythonu vyžaduje určité znalosti. Níže jsem uvedl některé koncepty, které byste již měli znát, abyste měli z tohoto tutoriálu smysl. Připojil jsem také zdroje, kde si můžete v případě potřeby oprášit koncepty.

Základní Python

Toto téma je spíše středně pokročilé/pokročilé téma. V důsledku toho byste před pokusem o učení měli být obeznámeni se základy Pythonu, jako jsou datové typy, funkce, objekty a třídy.

Měli byste také rozumět některým objektově orientovaným pojmům, jako jsou getry, settery a konstruktory. Pokud neznáte programovací jazyk Python, zde je několik zdrojů, které vám pomohou začít.

Funkce jsou prvotřídní občané

Kromě základního Pythonu byste si měli být vědomi i tohoto pokročilejšího konceptu v Pythonu. Funkce a v podstatě všechno ostatní v Pythonu jsou objekty jako int nebo string. Protože jsou to předměty, můžete s nimi dělat několik věcí, konkrétně:

  • Funkci můžete předat jako argument jiné funkci stejným způsobem, jakým předáváte řetězec nebo int jako argument funkce.
  • Funkce mohou být také vráceny jinými funkcemi, jako byste vrátili jiné hodnoty typu řetězec nebo int.
  • Funkce mohou být uloženy v proměnných

Ve skutečnosti jediný rozdíl mezi funkčními objekty a jinými objekty je, že funkční objekty obsahují magickou metodu __call__().

Doufejme, že v tomto bodě jste spokojeni s nezbytnými znalostmi. Můžeme začít diskutovat o hlavním tématu.

Co je to Python Decorator?

Dekorátor Pythonu je jednoduše funkce, která přebírá funkci jako argument a vrací upravenou verzi funkce, která byla předána. Jinými slovy, funkce foo je dekorátor, pokud jako argument přebírá panel funkcí. a vrátí jinou funkci baz.

Funkce baz je modifikací baru v tom smyslu, že v těle baz je volání funkčního pruhu. Nicméně před a po zavolání do baru může baz dělat cokoliv. To bylo sousto; zde je nějaký kód pro ilustraci situace:

# 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 vytvořit dekoratér v Pythonu?

Abych ilustroval, jak jsou dekorátory vytvářeny a používány v Pythonu, uvedu to na jednoduchém příkladu. V tomto příkladu vytvoříme funkci dekorátoru loggeru, která bude zaznamenávat název funkce, kterou zdobí, při každém spuštění této funkce.

  O 20 let později: Jak veřejná beta verze Mac OS X zachránila Mac

Pro začátek jsme vytvořili funkci dekoratér. Dekoratér bere funkci jako argument. func je funkce, kterou zdobíme.

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

Uvnitř funkce dekorátoru vytvoříme naši upravenou funkci, která zaznamená název func před spuštěním func.

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

Dále funkce create_logger vrátí upravenou funkci. V důsledku toho bude celá naše funkce create_logger vypadat takto:

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

    return modified_function

Dokončili jsme tvorbu dekoratérů. Funkce create_logger je jednoduchým příkladem funkce dekorátoru. Přijme funkci func, což je funkce, kterou zdobíme, a vrátí jinou funkci, upravená_func. Modified_func nejprve zaprotokoluje název funkce func a poté spustí funkci func.

Jak používat dekorátory v Pythonu

K použití našeho dekorátoru používáme syntaxi @ takto:

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

Nyní můžeme v našem skriptu zavolat say_hello() a výstupem by měl být následující text:

Calling:  say_hello
"Hello, World"

Ale co dělá @create_logger? No, aplikuje dekorátor na naši funkci say_hello. Abychom lépe porozuměli tomu, co dělá, kód bezprostředně pod tímto odstavcem by dosáhl stejného výsledku jako vložení @create_logger před say_hello.

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

say_hello = create_logger(say_hello)

Jinými slovy, jedním ze způsobů, jak používat dekorátory v Pythonu, je explicitně zavolat dekoratér předávající funkci, jak jsme to udělali ve výše uvedeném kódu. Druhým a výstižnějším způsobem je použití syntaxe @.

V této části jsme se zabývali tím, jak vytvořit dekorátory Python.

Trochu složitější příklady

Výše uvedený příklad byl jednoduchý případ. Existují trochu složitější příklady, jako když funkce, kterou zdobíme, přijímá argumenty. Další složitější situace je, když chcete vyzdobit celou třídu. Obě tyto situace zde popíšu.

Když funkce přijímá argumenty

Když funkce, kterou zdobíte, přijímá argumenty, upravená funkce by měla argumenty přijmout a předat je, když nakonec provede volání neupravené funkce. Pokud to zní matoucí, dovolte mi to vysvětlit v pojmech foo-bar.

Připomeňme, že foo je dekorační funkce, bar je funkce, kterou zdobíme, a baz je zdobený bar. V takovém případě bar vezme argumenty a předá je baz během volání do baz. Zde je příklad kódu pro upevnění konceptu:

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

Pokud *args a **kwargové vypadají neznáme; jsou to jednoduše ukazatele na poziční argumenty a argumenty klíčových slov.

Je důležité poznamenat, že baz má přístup k argumentům, a proto může provést určitou validaci argumentů před voláním bar.

Příkladem by bylo, kdybychom měli dekorační funkci, secure_string, která by zajistila, že argument předaný funkci, kterou zdobí, je řetězec; implementovali bychom to takto:

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

Funkci say_hello bychom mohli ozdobit takto:

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

Pak bychom mohli otestovat kód pomocí tohoto:

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

A měl by produkovat následující výstup:

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

Jak se očekávalo, skript dokázal vytisknout ‚Ahoj John‘, protože ‚John‘ je řetězec. Při pokusu o tisk „Ahoj 3“ vyhodilo výjimku, protože „3“ nebyl řetězec. Dekorátor secure_string lze použít k ověření argumentů jakékoli funkce, která vyžaduje řetězec.

  Jak změnit domovskou stránku v prohlížeči Google Chrome

Zdobení třídy

Kromě samotných dekoračních funkcí můžeme také vyzdobit třídy. Když do třídy přidáte dekorátor, nahradí dekorovaná metoda metodu konstruktoru/iniciátoru třídy (__init__).

Vraťme se k foo-baru, předpokládejme, že foo je náš dekoratér a Bar je třída, kterou zdobíme, pak foo vyzdobí bar.__init__. To se bude hodit, pokud chceme něco udělat před vytvořením instance objektů typu Bar.

To znamená, že následující kód

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

Je ekvivalentní k

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

Ve skutečnosti by vytvoření instance objektu třídy Bar, definovaného pomocí jedné ze dvou metod, mělo poskytnout stejný výstup:

Doing some stuff before instantiation
In initiator

Příklad dekorátorů v Pythonu

I když si můžete definovat své vlastní dekorátory, existují některé, které jsou již zabudovány do Pythonu. Zde jsou některé z běžných dekorátorů, se kterými se můžete v Pythonu setkat:

@statická metoda

Statická metoda se používá u třídy k označení, že metoda, kterou zdobí, je statická metoda. Statické metody jsou metody, které lze spustit bez nutnosti vytvářet instanci třídy. V následujícím příkladu kódu vytvoříme třídu Dog se statickou metodou bark.

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

Nyní lze k metodě kůry přistupovat takto:

Dog.bark()

A spuštění kódu by vytvořilo následující výstup:

Woof, woof!

Jak jsem již zmínil v části Jak používat dekoratéry, dekoratéry lze použít dvěma způsoby. Syntaxe @ je stručnější a je jedna z těchto dvou. Další metodou je zavolat funkci dekorátoru a předat funkci, kterou chceme ozdobit, jako argument. To znamená, že výše uvedený kód dosahuje stejné věci jako kód níže:

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

Dog.bark = staticmethod(Dog.bark)

A metodu kůry můžeme používat stále stejným způsobem

Dog.bark()

A vyprodukovalo by to stejný výstup

Woof, woof!

Jak vidíte, první metoda je čistší a je zjevnější, že funkce je statická funkce ještě předtím, než začnete číst kód. V důsledku toho pro zbývající příklady použiji první metodu. Ale nezapomeňte, že druhá metoda je alternativou.

@třídní metoda

Tento dekorátor se používá k označení, že metoda, kterou zdobí, je metoda třídy. Metody třídy jsou podobné statickým metodám v tom, že obě nevyžadují vytvoření instance třídy před jejich voláním.

  Jak opravit, že se na iPhonu nezobrazuje „Důvěřujte tomuto počítači“.

Hlavní rozdíl je však v tom, že metody třídy mají přístup k atributům třídy, zatímco statické metody nikoli. Je to proto, že Python automaticky předává třídu jako první argument metodě třídy, kdykoli je volána. K vytvoření metody třídy v Pythonu můžeme použít dekorátor classmethod.

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

Pro spuštění kódu jednoduše zavoláme metodu bez vytváření instance třídy:

Dog.what_are_you()

A výstup je:

I am a Dog!

@vlastnictví

Dekorátor vlastností se používá k označení metody jako nastavovače vlastností. Vraťme se k našemu příkladu psa, pojďme vytvořit metodu, která získá jméno 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

Nyní máme přístup ke jménu psa jako normální vlastnost,

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

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

A výsledek spuštění kódu by byl

The dog's name is: foo

@property.setter

Dekorátor property.setter se používá k vytvoření metody setter pro naše vlastnosti. Chcete-li použít dekorátor @property.setter, nahradíte vlastnost názvem vlastnosti, pro kterou vytváříte setter. Pokud například vytváříte setter pro metodu pro vlastnost foo, váš dekoratér bude @foo.setter. Zde je příklad psa pro ilustraci:

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

K otestování setteru můžeme použít následující kód:

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

Spuštění kódu vytvoří následující výstup:

The dogs's new name is: bar

Význam dekorátorů v Pythonu

Nyní, když jsme probrali, co jsou dekoratéři, a viděli jste některé příklady dekoratérů, můžeme diskutovat o tom, proč jsou dekoratéři v Pythonu tak důležití. Dekorátoři jsou důležití z několika důvodů. Některé z nich jsem uvedl níže:

  • Umožňují opětovné použití kódu: Ve výše uvedeném příkladu protokolování bychom mohli použít @create_logger na jakoukoli funkci, kterou chceme. To nám umožňuje přidat funkci protokolování ke všem našim funkcím, aniž bychom to museli ručně zapisovat pro každou funkci.
  • Umožňují vám psát modulární kód: Opět, vraťme se k příkladu protokolování, pomocí dekorátorů můžete oddělit základní funkci, v tomto případě say_hello, od další funkce, kterou potřebujete, v tomto případě od protokolování.
  • Vylepšují rámce a knihovny: Dekorátory jsou široce používány v rámcích a knihovnách Pythonu, aby poskytovaly další funkce. Například ve webových frameworkech, jako je Flask nebo Django, se dekorátory používají pro definování tras, zpracování autentizace nebo aplikaci middlewaru na konkrétní pohledy.

Závěrečná slova

Dekoratéři jsou neuvěřitelně užitečné; můžete je použít k rozšíření funkcí, aniž byste změnili jejich funkčnost. To je užitečné, když chcete načasovat výkon funkcí, přihlásit se při každém volání funkce, ověřit argumenty před voláním funkce nebo ověřit oprávnění před spuštěním funkce. Jakmile porozumíte dekorátorům, budete schopni psát kód čistším způsobem.

Dále si možná budete chtít přečíst naše články o n-ticích a používání cURL v Pythonu.