Как создать декораторы функций и объединить их?
Заголовок: Как создать два декоратора в Python для форматирования текста?
Описание проблемы:
Я пытаюсь создать два декоратора в Python, которые добавляют HTML-теги жирного и курсивного шрифта к строке, возвращаемой функцией. Мне нужно, чтобы декораторы применялись последовательно. Вот как я хотел бы это реализовать:
@make_bold
@make_italic
def say():
return "Hello"
При вызове функции say()
я ожидаю получить следующий результат:
"<b><i>Hello</i></b>"
Однако, я не знаю, как правильно реализовать декораторы make_bold
и make_italic
. Может ли кто-нибудь помочь с примером кода для этих декораторов?
4 ответ(ов)
Вы можете написать фабричную функцию, которая возвращает декоратор, оборачивающий возвращаемое значение декорируемой функции в тег, переданный фабричной функции. Вот пример:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator():
return '<%(tag)s>%(rv)s</%(tag)s>' % (
{'tag': tag, 'rv': func()})
return decorator
return factory
С помощью такой реализации вы сможете написать следующее:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
return 'hello'
Или можно сделать так:
makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')
@makebold
@makeitalic
def say():
return 'hello'
Лично я бы немного изменил реализацию декоратора:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator(val):
return func('<%(tag)s>%(val)s</%(tag)s>' %
{'tag': tag, 'val': val})
return decorator
return factory
Что позволит вам писать так:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
return val
say('hello')
Не забудьте, что синтаксис декораторов является сокращенной записью для следующей конструкции:
say = wrap_in_tag('b')(wrap_in_tag('i')(say))
Декораторы в Python действительно представляют собой просто синтаксический сахар.
Когда вы пишете код в таком виде:
@decorator
def func():
...
Это преобразуется в следующий код:
def func():
...
func = decorator(func)
Таким образом, декораторы позволяют более элегантно и лаконично оборачивать функции, но под капотом это просто вызов функции-декоратора с оригинальной функцией в качестве аргумента.
Да, вы можете возвращать лямбда-функции из функции-декоратора. В приведенном вами примере, декораторы makebold
и makeitalic
оборачивают функцию say
, добавляя к её результату HTML-теги для жирного и наклонного текста соответственно.
Вот как это работает:
def makebold(f):
return lambda: "<b>" + f() + "</b>"
def makeitalic(f):
return lambda: "<i>" + f() + "</i>"
@makebold
@makeitalic
def say():
return "Hello"
print(say())
- Когда вы вызываете
say()
, сначала выполняется декораторmakeitalic
, который оборачивает функциюsay
и возвращает новую функцию-лямбду, добавляющую тег<i>
к результату. - Затем выполняется декоратор
makebold
, который оборачивает результат работы предыдущего декоратора, добавляя тег<b>
. - В результате, при вызове
say()
, происходит следующее: сначала выполняется внутренняя функция, возвращающая строку"Hello"
, и к ней добавляются теги, в конечном итоге выводя"<b><i>Hello</i></b>"
.
Таким образом, вы получаете функцию say()
, которая при вызове возвращает текст с обернутыми в HTML-теги элементами.
Вопрос: Как можно реализовать декораторы для форматирования текста в HTML, например, для выделения текста жирным или курсивом?
Ответ: Существует несколько способов достижения этой цели. Предлагаю два варианта.
Первый способ — использование классов-декораторов для каждого стиля:
class bol(object):
def __init__(self, f):
self.f = f
def __call__(self):
return "<b>{}</b>".format(self.f())
class ita(object):
def __init__(self, f):
self.f = f
def __call__(self):
return "<i>{}</i>".format(self.f())
@bol
@ita
def sayhi():
return 'hi'
Во втором, более гибком варианте, мы можем создать один декоратор, который принимает тег в качестве аргумента:
class sty(object):
def __init__(self, tag):
self.tag = tag
def __call__(self, f):
def newf():
return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
return newf
@sty('b')
@sty('i')
def sayhi():
return 'hi'
Оба способа позволяют вам легко оборачивать функцию sayhi
в HTML-теги для отображения текста в разных стилях. Первый способ более прямолинейный, тогда как второй обеспечивает большую гибкость, так как позволяет использовать один класс для любого тега.
Как получить имя функции в виде строки?
Каковы преимущества использования лямбд? [закрыто]
Изменение типа столбца в pandas
Что такое __pycache__?
JavaScript: Знак плюс перед функциональным выражением