Треки
/
Python
Python
/
Салабус
/
Декоратори
Де

Декоратори у Python

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Декоратори

Декоратори - це функції, які приймають іншу функцію як аргумент, щоб розширити або замінити поведінку переданої функції. Якщо функція A є декоратором, а функція B - її аргументом, то функція A змінює, розширює або замінює поведінку функції B, не змінюючи код функції B. Кажуть, що функція-декоратор A обгортає функцію B. Хоча ми говоримо про «зміну» поведінки, обгорнута функція насправді не змінюється. Поведінку або додають навколо обгорнутої функції (і того, що вона повертає), або поведінку обгорнутої функції підмінюють іншою поведінкою.

Декоратор - це функція вищого порядку

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

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

Символ @ ставлять перед назвою функції-декоратора і розміщують безпосередньо над функцією, яку потрібно декорувати, ось так:

@decorator
def decorated_function():
    pass

Деякі декоратори приймають аргументи:

@decorator_with_arg(name="Bob")
def decorated_function2():
    pass

Якщо декоратор має визначені типові аргументи, у виклику @decorator() потрібно використати дужки, щоб декоратор працював, як і під час виклику будь-якої функції:

@decorator_with_default_arg()
def decorated_function3():
    pass

Якщо декоратор приймає позиційні аргументи, їх неподання призведе до помилки, яка матиме приблизно такий вигляд:

TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'

Синтаксис @decorator - це синтаксичний цукор або скорочення для виклику функції-декоратора і передавання їй декорованої функції як аргументу. Нижче наведено приклади альтернативних способів виклику декоратора:

def function():
    pass

function = decorator(function)


def function2():
    pass

function2 = decorator_with_arg(name="Bob")(function2)


def function3():
    pass

function3 = decorator_with_default_arg()(function3)

Як написати простий декоратор

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

Декоратори - це функції, які приймають щонайменше один аргумент: функцію, яку вони обгортають. Зазвичай вони повертають або обгорнуту функцію, або результат виразу, який використовує обгорнуту функцію.

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

>>> def do_nothing(func):
...     return func
...
... @do_nothing
... def function4():
...     return 4
...
>>> print(function4())
4

Декоратор може лише додавати побічні ефекти, як-от додаткову інформацію для журналювання:

>>> def my_logger(func):
...     print(f"Entering {func.__name__}")
...     return func
...
... @my_logger
... def my_func():
...     print("Hello")
...
>>> my_func()
Entering my_func
Hello

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

Внутрішні функції

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

Декоратор для перевірки з використанням внутрішньої функції

Нижче наведено приклад декоратора, який використовують для перевірки:

def my_validator(func):
    def my_wrapper(world):
        print(f"Entering {func.__name__} with {world} argument")
        
        if world == "Pluto":
            print("Pluto is not a planet!")
        else:
            return func(world)
    return my_wrapper

@my_validator
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!

У першому рядку маємо визначення аргументу func декоратора. У наступному рядку міститься визначення внутрішньої функції декоратора, яка обгортає аргумент func. Оскільки внутрішня функція обгортає аргумент func декоратора, їй передають той самий аргумент, який передають func. Зауважмо, що обгортка не зобовʼязана використовувати ту саму назву для аргументу, який було визначено в func. Початкова функція використовує planet, а декоратор - world, і декоратор усе одно працює.

Внутрішня функція або повертає func, або, якщо world == "Pluto", виводить, що Плутон не планета. Замість цього її можна написати так, щоб вона спричиняла ValueError. Отже, внутрішня функція обгортає func і повертає або func, або робить щось, що замінює те, що зробила б func. Декоратор повертає свою внутрішню функцію. Внутрішня функція може повертати або не повертати початкову, передану функцію. Залежно від того, який код умовно виконується у функції-обгортці чи внутрішній функції, func може бути повернуто, може бути спричинено помилку, або може бути повернуто значення типу, який повертає func.

Як декорувати функцію, що приймає довільну кількість аргументів

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

Нижче наведено приклад декоратора для функції, що приймає довільну кількість аргументів:

def double(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

@double
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 18

print(add(2, 3, 4, 5, 6))
#-> 40

Це працює для подвоєння поверненого значення функції-аргументу. Якщо ми хочемо потроїти, почетверити тощо повернене значення, ми можемо додати параметр до самого декоратора, як показано в наступному розділі.

Декоратори, які мають власні параметри

Нижче наведено приклад декоратора, який можна налаштувати так, щоб він множив повернене значення декорованої функції на довільне число:

def multi(factor=1):
     if factor == 0:
        raise ValueError("factor must not be 0")

     def outer_wrapper(func):
         def inner_wrapper(*args, **kwargs):
             return func(*args, **kwargs) * factor
         return inner_wrapper
     return outer_wrapper

@multi(factor=3)
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 27

print(add(2, 3, 4, 5, 6))
#-> 60

Перші рядки перевіряють, що factor не дорівнює 0. Потім визначають зовнішню обгортку. Вона має ту саму сигнатуру, яку ми очікуємо для декоратора без параметрів. Зовнішня обгортка має внутрішню функцію з тією самою сигнатурою, що й початкова функція. Внутрішня обгортка виконує роботу з множення поверненого значення початкової функції на аргумент, переданий декоратору. Зовнішня обгортка повертає внутрішню обгортку, а декоратор повертає зовнішню обгортку.

Нижче наведено приклад параметризованого декоратора, який керує тим, чи перевіряє він аргумент, переданий початковій функції:

def check_for_pluto(check=True):
    def my_validator(func):
        def my_wrapper(world):
            print(f"Entering {func.__name__} with {world} argument")
            if check and world == "Pluto":
                print("Pluto is not a planet!")
            else:
                return func(world)
        
        return my_wrapper
    return my_validator

@check_for_pluto(check=False)
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!

Це дає змогу легко перемикатися між перевіркою на Pluto і її відсутністю, і робиться це без потреби змінювати my_func.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці