'ТРАССИРОВКА: вызвана say() с ("Джейн", "Привет, Мир"), {}'
'ТРАССИРОВКА: say() вернула "Джейн: Привет, Мир"'
'Джейн: Привет, Мир'
Если говорить об отладке, то существует две вещи, которые при отладке
декораторов следует иметь в виду.
Как писать «отлаживаемые» декораторы
При использовании декоратора вы на самом деле только подменяете одну
функцию другой. Оборотной стороной этого процесса является то, что он
«скрывает» некоторые метаданные, закрепленные за оригинальной (не-
декорированной) функцией.
90 Глава 3 • Эффективные функции
Например, оригинальное имя функции, ее строка документации docstring
и список параметров скрыты замыканием-оберткой:
def greet():
"""Вернуть дружеское приветствие."""
return 'Привет!'
decorated_greet = uppercase(greet)
При попытке получить доступ к каким-либо из этих метаданных функции
вместо них вы увидите метаданные замыкания-обертки:
>>> greet.__name__
'greet'
>>> greet.__doc__
'Вернуть дружеское приветствие.'
>>> decorated_greet.__name__
'wrapper'
>>> decorated_greet.__doc__
None
Это делает отладку и работу с интерпретатором Python неуклюжей и тру-
доемкой. К счастью, существует быстрое решение этой проблемы: деко-
ратор
functools.wraps
, включенный в стандартную библиотеку Python
1
.
Декоратор
functools.wraps
можно использовать в своих собственных
декораторах для того, чтобы копировать потерянные метаданные из не-
декорированной функции в замыкание декоратора. Вот пример:
import functools
def uppercase(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
Применение декоратора
functools.wraps
к замыканию-обертке, возвра-
щаемому декоратором, переносит в него строку документации и другие
метаданные входной функции:
1
См. документацию Python «functools»:
https://docs .python .org/3/library/functools .html
3 .3 . Сила декораторов 91
@uppercase def greet():
"""Вернуть дружеское приветствие."""
return 'Привет!'
>>> greet.__name__
'greet'
>>> greet.__doc__
'Вернуть дружеское приветствие.'
В качестве оптимального практического приема я порекомендовал бы ис-
пользовать декоратор
functools.wraps
во всех декораторах, которые вы
пишете сами. Это не займет много времени и уменьшит головную боль
вам (и другим) в будущем при отладке.
Да, и поздравляю — вы успешно добрались до самого конца этого слож-
ного раздела и многое узнали о декораторах в Python. Отличная работа!
Ключевые выводы
Декораторы определяют структурные блоки многократного исполь-
зования, которые можно применять к вызываемому объекту с целью
модификации его поведения без необратимого изменения самого вы-
зываемого объекта.
Синтаксис
@
является всего-навсего сокращенной записью для вызова
декоратора с входной функцией. Многочисленные декораторы, разме-
щенные над одной-единственной функцией, применяются снизу-вверх
(стековая укладка декораторов).
В качестве оптимального практического приема отладки исполь-
зуйте в своих собственных декораторах вспомогательный декоратор
functools.wraps
, чтобы переносить метаданные из недекорированного
вызываемого объекта в декорированный.
Точно так же, как и любой другой инструмент в комплекте инструмен-
тов разработки программного обеспечения, декораторы не являются
панацеей, и ими не стоит злоупотреблять. Важно уравновесить необ-
ходимость «довести дело до конца» с целевой установкой «не увязнуть
в ужасной и неудобной в обслуживании мешанине кодовой базы».
92 Глава 3 • Эффективные функции
3 .4 . Веселье с *args и **kwargs
Однажды я программировал в паре с суровым питонистом, который то
и дело восклицал «арррг!» и «кварг!» всякий раз, когда набирал опре-
деление функции с необязательными или именованными параметрами.
Во всем остальном мы отлично поладили. М-да, вот что в итоге делает
с людьми программирование в академии...
Нужно сказать, что сколько бы ни потешались над параметрами
*args
и
**kwargs
, тем не менее они являются очень полезным функциональным
средством языка Python. И понимание их потенциала сделает из вас более
эффективного разработчика.
Итак, для чего же используются параметры
*args
и
**kwargs
? Они по-
зволяют функции принимать необязательные аргументы, благодаря чему
вы можете создавать гибкие API в модулях и классах:
def foo(required, *args, **kwargs):
print(required)
if args:
print(args)
if kwargs:
print(kwargs)
Приведенная выше функция требует по крайней мере одного аргумента
под названием «
required
», то есть обязательный, но она также может при-
нимать дополнительные позиционные и именованные аргументы.
Если мы вызовем функцию с дополнительными аргументами, то
args
соберет дополнительные позиционные аргументы в кортеж, потому что
имя параметра имеет префикс
*
.
Аналогичным образом,
kwargs
соберет дополнительные именованные
аргументы в словарь, потому что имя параметра имеет префикс
**
.
Как
args
, так и
kwargs
могут быть пустыми, если никакие дополнительные
аргументы в функцию не переданы.
Когда мы вызываем функцию с различными комбинациями аргументов,
вы видите, как Python собирает их в параметрах
args
и
kwargs
в соответ-
3 .4 . Веселье с *args и **kwargs 93
ствии с тем, являются они позиционными или именованными аргумен-
тами:
>>> foo()
Do'stlaringiz bilan baham: |