Пояснення (з прикладами та випадками використання)

Декоратори Python є неймовірно корисною конструкцією в Python. Використовуючи декоратори в Python, ми можемо змінити поведінку функції, загорнувши її в іншу функцію. Декоратори дозволяють нам писати чистіший код і ділитися функціями. Ця стаття є навчальним посібником не лише про те, як використовувати декоратори, але й про те, як їх створювати.

Необхідні знання

Тема декораторів у Python вимагає певних базових знань. Нижче я перерахував деякі поняття, з якими ви повинні бути знайомі, щоб зрозуміти цей посібник. Я також зв’язав ресурси, де ви можете освіжити концепції, якщо потрібно.

Базовий Python

Ця тема є більш середньою/досконалою. Як результат, перш ніж намагатися вчитися, ви вже повинні бути знайомі з основами Python, такими як типи даних, функції, об’єкти та класи.

Ви також повинні розуміти деякі об’єктно-орієнтовані концепції, такі як геттери, сетери та конструктори. Якщо ви не знайомі з мовою програмування Python, ось деякі ресурси, щоб почати.

Функції – громадяни першого класу

Окрім базового Python, ви також повинні знати про цю більш просунуту концепцію в Python. Функції та майже все інше в Python є об’єктами, такими як int або string. Оскільки вони є об’єктами, ви можете робити з ними декілька речей, а саме:

  • Ви можете передати функцію як аргумент іншій функції так само, як ви передаєте рядок або int як аргумент функції.
  • Функції також можуть бути повернуті іншими функціями, як ви повернули б інші значення рядка або int.
  • Функції можна зберігати в змінних

Фактично, єдина відмінність між функціональними об’єктами та іншими об’єктами полягає в тому, що функціональні об’єкти містять магічний метод __call__().

Сподіваємося, що на даний момент ви впоралися з необхідними знаннями. Можемо приступати до обговорення основної теми.

Що таке декоратор Python?

Декоратор Python — це просто функція, яка приймає функцію як аргумент і повертає модифіковану версію переданої функції. Іншими словами, функція foo є декоратором, якщо вона приймає як аргумент панель функцій і повертає іншу функцію baz.

Функція baz є модифікацією bar у тому сенсі, що в тілі baz є виклик панелі функцій. Однак до і після дзвінка в бар Баз може робити що завгодно. Це був ковток; ось код для ілюстрації ситуації:

# 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

Як створити декоратор у Python?

Щоб проілюструвати, як створюються та використовуються декоратори в Python, я збираюся проілюструвати це простим прикладом. У цьому прикладі ми створимо функцію-декоратор реєстратора, який буде реєструвати назву функції, яку він декорує, щоразу, коли ця функція запускається.

  9 приватних серверів World of Warcraft (WOW), до яких можна приєднатися

Для початку ми створили функцію декоратора. Декоратор використовує func як аргумент. func — функція, яку ми прикрашаємо.

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

Усередині функції декоратора ми збираємося створити нашу модифіковану функцію, яка записуватиме назву func перед запуском func.

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

Далі функція create_logger поверне змінену функцію. У результаті вся наша функція create_logger виглядатиме так:

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

    return modified_function

Ми закінчили створення декоратора. Функція create_logger є простим прикладом функції декоратора. Він приймає func, яка є функцією, яку ми прикрашаємо, і повертає іншу функцію, modified_func. modified_func спочатку записує назву func, перш ніж запустити func.

Як використовувати декоратори в Python

Щоб використовувати наш декоратор, ми використовуємо синтаксис @ таким чином:

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

Тепер ми можемо викликати say_hello() у нашому сценарії, і результатом має бути такий текст:

Calling:  say_hello
"Hello, World"

Але що робить @create_logger? Ну, це застосування декоратора до нашої функції say_hello. Щоб краще зрозуміти, що робиться, код безпосередньо під цим абзацом досягне того самого результату, що й @create_logger перед say_hello.

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

say_hello = create_logger(say_hello)

Іншими словами, один із способів використання декораторів у Python — це явно викликати декоратор, передаючи функцію, як ми робили в коді вище. Інший і більш стислий спосіб – використовувати синтаксис @.

У цьому розділі ми розглянули, як створити декоратори Python.

Трохи складніші приклади

Наведений вище приклад був простим випадком. Є трохи складніші приклади, наприклад, коли функція, яку ми декоруємо, приймає аргументи. Ще одна складніша ситуація, коли ви хочете прикрасити цілий клас. Я збираюся розглянути обидві ці ситуації тут.

Коли функція приймає аргументи

Коли функція, яку ви прикрашаєте, приймає аргументи, модифікована функція повинна отримати аргументи та передати їх, коли вона зрештою викличе незмінену функцію. Якщо це звучить заплутано, дозвольте мені пояснити в термінах foo-bar.

Пам’ятайте, що foo — це функція декоратора, bar — функція, яку ми декоруємо, а baz — декорована панель. У цьому випадку bar візьме аргументи та передасть їх baz під час виклику baz. Ось приклад коду для зміцнення концепції:

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

Якщо *args і **kwargs виглядають незнайомими; вони є просто покажчиками на позиційні та ключові аргументи відповідно.

Важливо зауважити, що baz має доступ до аргументів і тому може виконувати певну перевірку аргументів перед викликом bar.

Прикладом може бути, якби у нас була функція декоратора, secure_string, яка гарантувала б, що аргумент, переданий функції, яку вона декорує, є рядком; ми б реалізували це так:

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

Ми могли б прикрасити функцію say_hello так:

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

Тоді ми могли б протестувати код за допомогою цього:

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

І він повинен вивести наступний результат:

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

Як і очікувалося, сценарій вдалося надрукувати «Hello John», оскільки «John» є рядком. Він викликав виняток під час спроби надрукувати «Hello 3», оскільки «3» не був рядком. Декоратор secure_string можна використовувати для перевірки аргументів будь-якої функції, яка вимагає рядок.

  9 найкращих плагінів SEO для WordPress

Оформлення класу

Окрім просто декоративних функцій, ми також можемо декорувати класи. Коли ви додаєте декоратор до класу, декорований метод замінює метод конструктора/ініціатора класу (__init__).

Повертаючись до foo-bar, припустимо, foo — наш декоратор, а Bar — клас, який ми декоруємо, тоді foo прикрасить Bar.__init__. Це буде корисно, якщо ми хочемо зробити щось до того, як буде створено екземпляр об’єктів типу Bar.

Це означає, що наступний код

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

Є еквівалентом

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

Фактично, створення екземпляра об’єкта класу Bar, визначеного за допомогою будь-якого з двох методів, має дати вам той самий результат:

Doing some stuff before instantiation
In initiator

Приклади декораторів у Python

Хоча ви можете визначити власні декоратори, є деякі, які вже вбудовані в Python. Ось деякі з типових декораторів, які ви можете зустріти в Python:

@staticmethod

Статичний метод використовується в класі, щоб вказати, що метод, який він прикрашає, є статичним методом. Статичні методи — це методи, які можуть працювати без необхідності створення екземпляра класу. У наступному прикладі коду ми створюємо клас Dog зі статичним методом bark.

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

Тепер до методу bark можна отримати доступ так:

Dog.bark()

І виконання коду дасть наступний результат:

Woof, woof!

Як я згадував у розділі «Як використовувати декоратори», декоратори можна використовувати двома способами. Синтаксис @ є більш лаконічним і є одним із двох. Інший метод полягає у виклику функції декоратора, передаючи функцію, яку ми хочемо прикрасити, як аргумент. Це означає, що код вище досягає того самого, що й код нижче:

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

Dog.bark = staticmethod(Dog.bark)

І ми все ще можемо використовувати метод кори таким же чином

Dog.bark()

І це дасть той самий результат

Woof, woof!

Як ви бачите, перший метод чистіший і більш очевидно, що функція є статичною ще до того, як ви навіть почали читати код. У результаті для решти прикладів я буду використовувати перший спосіб. Але пам’ятайте, що другий спосіб є альтернативним.

@classmethod

Цей декоратор використовується, щоб вказати, що метод, який він декорує, є методом класу. Методи класу подібні до статичних методів тим, що вони обидва не вимагають створення екземпляра класу перед їх викликом.

Однак основна відмінність полягає в тому, що методи класу мають доступ до атрибутів класу, а статичні методи – ні. Це пояснюється тим, що Python автоматично передає клас як перший аргумент методу класу під час кожного його виклику. Щоб створити метод класу в Python, ми можемо використати декоратор classmethod.

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

Щоб запустити код, ми просто викликаємо метод без створення екземпляра класу:

Dog.what_are_you()

І результат:

I am a Dog!

@власність

Декоратор властивостей використовується для позначення методу як параметра властивості. Повертаючись до нашого прикладу з собакою, давайте створимо метод, який отримує ім’я собаки.

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 an instance of the class
foo = Dog('foo')

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

І результат виконання коду буде

The dog's name is: foo

@property.setter

Декоратор property.setter використовується для створення методу налаштування для наших властивостей. Щоб використовувати декоратор @property.setter, ви замінюєте властивість назвою властивості, для якої ви створюєте сеттер. Наприклад, якщо ви створюєте установщик для методу для властивості foo, вашим декоратором буде @foo.setter. Ось приклад собаки для ілюстрації:

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

Щоб перевірити сетер, ми можемо використати такий код:

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

Виконання коду дасть наступний результат:

The dogs's new name is: bar

Важливість декораторів у Python

Тепер, коли ми розглянули, що таке декоратори, і ви побачили кілька прикладів декораторів, ми можемо обговорити, чому декоратори важливі в Python. Декоратори важливі з кількох причин. Деякі з них я перерахував нижче:

  • Вони дозволяють багаторазове використання коду: у прикладі журналювання, наведеному вище, ми можемо використовувати @create_logger для будь-якої функції, яку ми хочемо. Це дозволяє нам додати функцію ведення журналу до всіх наших функцій, не записуючи її вручну для кожної функції.
  • Вони дозволяють вам писати модульний код: знову ж таки, повертаючись до прикладу журналювання, за допомогою декораторів ви можете відокремити основну функцію, у цьому випадку say_hello, від іншої потрібної вам функції, у цьому випадку журналювання.
  • Вони вдосконалюють фреймворки та бібліотеки: декоратори широко використовуються в фреймворках і бібліотеках Python для надання додаткових функцій. Наприклад, у таких веб-фреймворках, як Flask або Django, декоратори використовуються для визначення маршрутів, обробки автентифікації або застосування проміжного ПЗ до певних представлень.
  Як навчити Face ID бути більш точним

Заключні слова

Декоратори неймовірно корисні; ви можете використовувати їх для розширення функцій, не змінюючи їхньої функціональності. Це корисно, коли потрібно відстежувати час виконання функцій, реєструвати кожен виклик функції, перевіряти аргументи перед викликом функції або перевіряти дозволи перед запуском функції. Коли ви зрозумієте декораторів, ви зможете писати код чистішим способом.

Далі ви можете прочитати наші статті про кортежі та використання cURL у Python.