Python

Использование functool.wraps в декораторах Python

Spread the love

Давайте предположим, что у нас есть простой декоратор, «mydeco», который берет вывод функции и помещает его в строку, за которой следуют три восклицательных знака:

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(args, **kwargs)}!!!'
    return wrapper

Давайте теперь используем наш декоратор для двух разных функций:

@mydeco
def add(a, b):
    '''Add two objects together, the long way'''
    return a + b
@mydeco
def mysum(*args):
    '''Sum any numbers together, the long way'''
    total = 0
    for one_item in args:
        total += one_item
    return total

Что происходит, когда я запускаю эти функции? Они работают как мы ожидали:

>>> add(10, 20)
'30!!!'
>>> mysum(10, 20, 30, 40, 50)
'150!!!

Фантастика! Мы возвращаем результат каждой функции в виде строки с восклицательными знаками. Декоратор работает.

Но есть несколько проблем с тем, что мы сделали. Например, что если я запрашиваю каждую функцию о ее имени:

>>> add.__name__
'wrapper'
>>> mysum.__name__
'wrapper'

Атрибут __name__, который возвращает нам имя функции при ее определении, теперь отражает возвращенную внутреннюю функцию «wrapper» используемую в нашем декораторе. Это так и есть, но это не то что нам нужно.

Может быть еще хуже, если мы попросим вернуть строку документации:

>>> help(add)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
>>> help(mysum)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)

Другими словами: теперь мы получаем строку документации и функцию «wrapper», внутренней функции. И это проблема, потому что теперь мы не может получить основные атрибуты декорированной функции.

Мы можем решить эту проблему, хотя бы частично, назначив атрибуты __name__ и __doc__ в нашем декораторе:

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(args, **kwargs)}!!!'
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper

Если мы используем эту версию декоратора, то каждый раз, когда мы возвращаем «wrapper» от нашего декоратора, мы вручную присваиваем имя исходной функции и строку документации для нее.

>>> help(add)
Help on function add in module __main__:

add(*args, **kwargs)
     Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module __main__:

mysum(*args, **kwargs)
    Sum any numbers together, the long way

Хорошая новость заключается в том, что мы исправили проблему имен и строк документации. Но сигнатура функции все еще остается общей *args и **kwargs.

Решение заключается в использовании functools.wraps. Она предназначен для решения именно этих проблем. Ирония, конечно, заключается в том, что это может доставить больше проблем, чем обычно это делают декораторы, потому что functools.wraps — это … декоратор, который принимает аргумент! Вот как это выглядит:

from functools import wraps

def mydeco(func):
    @wraps(func)
    def wrapper(*args, *kwargs):
        return f'{func(args, **kwargs)}!!!'
    return wrapper

Обратите внимание на то, что мы сделали здесь: мы использовали декоратор «functool.wraps», чтобы декорировать нашу внутреннюю функцию, функцию «wrapper». Применяя этот декоратор «wraps» к нашей внутренней функции, мы копируем имя, строку документации и сигнатуру функции в нашу внутреннюю функцию, избегая проблем, с которыми мы сталкивались ранее:

>>> help(add)
Help on function add in module main:
add(a, b)
     Add two objects together, the long way

>>> help(mysum)
Help on function mysum in module main:
mysum(*args)
     Sum any numbers together, the long way

Ее использование почти ничего не стоит (т. е. одна строка кода) и она делает вашу декорированную функцию более естественной.

Оригинальная статья: Making your Python decorators even better, with functool.wraps

Была ли вам полезна эта статья?
[30 / 4.6]

Spread the love
Editorial Team

View Comments

  • Ошибка в
    return f'{func(args, **kwargs)}!!!'
    забыли * перед args

  • "это может доставить больше проблем, чем обычно это делают декораторы"

    было бы интересно узнать в этом месте поподробнее :)

    • плохо перевели, в оригинале имелось в виду что это еще сложнее осмыслить чем обычные декораторы

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

12 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

12 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago