31

Как создать декораторы функций и объединить их?

11

Заголовок: Как создать два декоратора в Python для форматирования текста?

Описание проблемы:

Я пытаюсь создать два декоратора в Python, которые добавляют HTML-теги жирного и курсивного шрифта к строке, возвращаемой функцией. Мне нужно, чтобы декораторы применялись последовательно. Вот как я хотел бы это реализовать:

@make_bold
@make_italic
def say():
   return "Hello"

При вызове функции say() я ожидаю получить следующий результат:

"<b><i>Hello</i></b>"

Однако, я не знаю, как правильно реализовать декораторы make_bold и make_italic. Может ли кто-нибудь помочь с примером кода для этих декораторов?

4 ответ(ов)

1

Вы можете написать фабричную функцию, которая возвращает декоратор, оборачивающий возвращаемое значение декорируемой функции в тег, переданный фабричной функции. Вот пример:

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

Декораторы в Python действительно представляют собой просто синтаксический сахар.

Когда вы пишете код в таком виде:

@decorator
def func():
    ...

Это преобразуется в следующий код:

def func():
    ...
func = decorator(func)

Таким образом, декораторы позволяют более элегантно и лаконично оборачивать функции, но под капотом это просто вызов функции-декоратора с оригинальной функцией в качестве аргумента.

0

Да, вы можете возвращать лямбда-функции из функции-декоратора. В приведенном вами примере, декораторы 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())
  1. Когда вы вызываете say(), сначала выполняется декоратор makeitalic, который оборачивает функцию say и возвращает новую функцию-лямбду, добавляющую тег <i> к результату.
  2. Затем выполняется декоратор makebold, который оборачивает результат работы предыдущего декоратора, добавляя тег <b>.
  3. В результате, при вызове say(), происходит следующее: сначала выполняется внутренняя функция, возвращающая строку "Hello", и к ней добавляются теги, в конечном итоге выводя "<b><i>Hello</i></b>".

Таким образом, вы получаете функцию say(), которая при вызове возвращает текст с обернутыми в HTML-теги элементами.

0

Вопрос: Как можно реализовать декораторы для форматирования текста в 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-теги для отображения текста в разных стилях. Первый способ более прямолинейный, тогда как второй обеспечивает большую гибкость, так как позволяет использовать один класс для любого тега.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь