Аргументы в пользу декораторов Python

Итак, со следующим ...

    def makeBold(fn):
        def wrapped():
            return '<b>'+fn()+'</b>'
        return wrapped

    @makeBold
    def produceElement():
        return 'hello'

Результат

    <b>hello</b>

Я бы хотел сделать что-то подобное ...

    @makeBold(attrib=val, attrib=val)
    def produceElement():
        return 'hello'

и в результате получится что-то вроде ...

    <b attrib=val, attrib=val>hello<b/>

Любой совет будет полезен!


person Luis De Siqueira    schedule 19.07.2013    source источник


Ответы (3)


Оберните вашу функцию другой функцией:

import functools
def makeBold(**kwargs):
    attribstrings = str(kwargs) # figure out what to do with the dict yourself 
    def actualdecorator(fn):
        @functools.wraps(fn)
        def wrapped():
            return '<b'+attribstrings+'>'+fn()+'</b>'
        return wrapped
    return actualdecorator

Я оставляю работу над построением струны в качестве упражнения для читателя.

Обратите внимание, что структура выражения декоратора - @ <callable object of one parameter, w> <declaration of callable object, f>. Это эффект f = w(f). Соответственно, w (декоратор) должен возвращать вызываемый объект того же типа, что и f.

В @makebold(foo)def bar(x):pass выражение makebold(foo) является декоратором, то есть конечным эффектом декоратора является bar = makebold(foo)(bar), и поэтому bar в конечном итоге содержит wrapped.

Цель functools.wraps - исправить свойства декорированной функции для копирования метаданных (таких как имя и строка документации) из функции аргумента в упакованную функцию, чтобы весь процесс упаковки был прозрачным.

person Marcin    schedule 19.07.2013
comment
Наверное, не помешало бы засунуть туда functools.wraps ... - person Jon Clements♦; 20.07.2013
comment
@JonClements Хорошее замечание. - person Marcin; 20.07.2013
comment
Можете ли вы объяснить слои декораторов, включая декоратор functools? - person Stephan; 20.07.2013
comment
@Stephan У декоратора functools есть документация, адекватно объясняющая его. Тем не менее, объяснение почему структуры решения должно быть таким, может быть хорошей идеей. - person millimoose; 20.07.2013
comment
Функция, которая возвращает функцию, которая принимает функцию и возвращает функцию. Я называю это функциональным программированием! - person rodrigo; 20.07.2013
comment
@rodrigo Как ни странно, типобезопасные языки программирования FP на самом деле не могут выразить этот шаблон. То есть, по крайней мере, в простой модели, где каждая функция имеет фиксированное количество типизированных параметров, нет способа выразить f - это функция, которая принимает в качестве параметра функцию g -> ... -> a и возвращает функцию h -> ... -> b, где ... - произвольный набор параметров. - person millimoose; 20.07.2013
comment
@rodrigo Следующий шаг, давайте изучим точечное программирование на Python! (На самом деле это довольно просто). - person Marcin; 20.07.2013
comment
Какой смысл в бесплатном питоне? И вы можете указать мне документ об этом ... бесплатно? :-) - person Luis De Siqueira; 23.07.2013
comment
@LuisDeSiqueira Бесточечное программирование - это стиль программирования, в котором вы составляете функции, поэтому вы не ссылаетесь напрямую на аргументы. См .: haskell.org/haskellwiki/Pointfree и stackoverflow.com/questions/944446/ - person Marcin; 23.07.2013

Я, возможно, сомневаюсь, что это хороший вариант использования декораторов, но здесь:

import string

SelfClosing = object()

def escapeAttr(attr):
    # WARNING: example only, security not guaranteed for any of these functions
    return attr.replace('"', '\\"')

def tag(name, content='', **attributes):
    # prepare attributes
    for attr,value in attributes.items():
        assert all(c.isalnum() for c in attr)  # probably want to check xml spec
    attrString = ' '.join('{}="{}"'.format(k,escapeAttr(v)) for k,v in attributes.items())

    if not content==SelfClosing:
        return '<{name} {attrs}>{content}</{name}>'.format(
            name = name,
            attrs = attrString,
            content = content
        )
    else:  # self-closing tag
        return '<{name} {attrs}/>'

Пример:

def makeBoldWrapper(**attributes):
    def wrapWithBold(origFunc):
        def composed(*args, **kw):
            result = origFunc(*args, **kw)
            postprocessed = tag('b', content=result, **attributes)
            return postprocessed
        return composed
    return wrapWithBold

Демо:

@makeBoldWrapper(attr1='1', attr2='2')
def helloWorld(text):
    return text

>>> print( helloWorld('Hello, world!') )
<b attr2="2" attr1="1">Hello, world!</b>

Распространенное заблуждение декораторов состоит в том, что параметры (attr1=...) являются параметрами декоратора @myDecorator; Это не относится к делу. Скорее результат вызова функции myDecoratorFactory(attr1=...) вычисляется как someresult и становится анонимным декоратором @someresult. Следовательно, «декораторы с аргументами» на самом деле являются фабриками декораторов, которым необходимо возвращать декоратор в качестве значения.

person ninjagecko    schedule 19.07.2013
comment
Вероятно, вам следует использовать functools.wraps в примерах кода. Это не обязательно, но, безусловно, лучший способ. - person millimoose; 20.07.2013
comment
@millimoose: Я лично испытываю к partial отношения любви и ненависти, поскольку в прошлом у меня с ним были проблемы. Я чувствую, что это что-то вроде глупости и разрушает сигнатуры методов, хотя каррирование - это хорошо. Несмотря на то, что @wraps существует, чтобы предположительно сохранить имя функции и строку документации, если бы я хотел этого, имхо, было бы лучше использовать «настоящую» структуру декоратора, которая все делает правильно. Ненужные декораторы также могут создавать значительные накладные расходы, которые я однажды пытался игнорировать, но потерпел неудачу. Однажды созданный фреймворк, который переписывает функцию и перекомпилирует ее в то, что «должно быть». - person ninjagecko; 20.07.2013
comment
@millimoose: [продолжение] Но, возможно, вы правы в том, что я должен использовать его ни по какой другой причине, кроме прямой совместимости, возможно, заменив его другим from myfuture import wraps, который можно было бы заменить для чего-то другого. Все еще не уверен, следует ли мне использовать его в примерах, потому что это может увеличить необходимые умственные способности, чтобы выяснить, что делают, поскольку он, по сути, ничего не делает (прошло некоторое время с тех пор, как я его использовал, возможно, я игнорирую * args или * * кВт удобство?). Спасибо за предложение. - person ninjagecko; 20.07.2013
comment
Начиная с Python 3.3, functools.wraps поддерживает сохранение подписей функций как видимых с помощью _ 2_. (К сожалению, не getargspec(), по какой-то причине эти два механизма не были унифицированы.) Итак, "настоящая" структура декоратора ", которую вы хотите, на самом деле wraps(). (Признаюсь, я не знал этого на первый взгляд, мне просто пришло в голову, что, возможно, кто-то из команды разработчиков Python подумал об этом - оказывается, они это сделали.) - person millimoose; 20.07.2013
comment
@millimoose: О, хорошо, спасибо. Затем я мог бы использовать его, когда производительность не является проблемой. - person ninjagecko; 20.07.2013

Чтобы сделать что-то подобное, вам понадобится функция, возвращающая функцию декоратора. Итак, в этом случае (при условии, что вы хотите принимать произвольные атрибуты) вы должны написать

def format_attribs(kwargs):
    """Properly formats HTML attributes from a dictionary"""
    return ' '.join('{}="{}"'.format(key, val) for key,val in kwargs.iteritems())

def makeBold(**kwargs):
    attribs = format_attribs(kwargs)
    def _makeBold(fn):
        def wrapped():
            return '<b ' + attribs + '>' + fn() + '</b>'
        return wrapped
    return _makeBold

Чтобы сделать эту makeBold функцию немного более общей, вы хотите передать аргументы в fn и сохранить другую информацию, такую ​​как имя функции, с помощью _ 4_:

import functools
def makeBold(**kwargs):
    attribs = format_attribs(kwargs)
    def _makeBold(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<b ' + attribs + '>' + fn(*args, **kwargs) + '</b>'
        return wrapped
    return _makeBold
person murgatroid99    schedule 19.07.2013