Python装饰器的参数

4

所以,根据以下步骤进行:

    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/>

有任何建议将不胜感激!

3个回答

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的目的是修复被装饰函数的属性,以从参数函数复制元数据(如名称和文档字符串)到包装函数,使整个包装过程透明。


也许在那里加上functools.wraps不会有什么坏处... - Jon Clements
1
@Stephan functools 装饰器有文档充分解释了它。尽管如此,解释为什么解决方案的结构必须看起来像这样可能是一个好主意。 - millimoose
1
一个返回函数的函数,它接受一个函数并返回一个函数。我称之为函数式编程! - rodrigo
有趣的是,类型安全的函数式编程语言实际上无法表达这种模式。换句话说,在每个函数都具有固定数量的类型参数的直接模型中,没有办法表达“f是一个以函数g -> ... -> a作为其参数,并返回一个函数h -> ... -> b的函数,其中...是一组任意参数”的概念。 - millimoose
@rodrigo 接下来,让我们在Python中探索pointfree编程!(实际上非常容易) - Marcin
显示剩余3条评论

2

我有点怀疑这是否是装饰器的好用例,但是在这里:

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。因此,“带参数的装饰器”实际上是需要返回装饰器值的装饰器工厂。

你在代码示例中应该使用functools.wraps。虽然不是必需的,但这显然是一种“最佳实践”。 - millimoose
@millimoose:就我个人而言,我对partial有着爱恨交加的感觉,因为我曾经遇到过它的问题。我觉得它有点笨重,会破坏方法签名,尽管柯里化是好的。即使@wraps存在于函数中,据说可以保留函数名称和文档字符串,但如果我想要这样做,我认为最好使用一个“真正”的装饰器框架,以确保一切正确。不必要的装饰器也可能会创建大量开销,我曾试图忽略它们,但失败了。我曾经制作过一个框架,将函数重写并重新编译成它应该的形式。 - ninjagecko
@millimoose: [续] 但也许你是对的,我应该使用它,没有其他原因,只是为了前向兼容性,也许将其替换为另一个from myfuture import wraps,可以交换以执行其他操作。不确定是否应该在示例中使用它,因为这可能会增加解决问题所需的脑力,因为它本质上什么都不做(已经有一段时间没有使用它了,也许我忽略了*args或**kw的便利性?)。感谢您的建议。 - ninjagecko
1
从Python 3.3开始,functools.wraps支持保留函数签名,可以通过inspect.signature()进行查看。(不幸的是,getargspec()没有这个功能,由于某些原因,这两种机制没有统一。) 因此,您所需要的“真正的装饰器框架”实际上就是wraps()。(我承认我并不是从头脑中知道这一点,只是想到可能有Python开发团队的人想到了这一点-结果他们确实想到了。) - millimoose
@millimoose:哦,不错,谢谢。当性能不是问题时,我可能会使用它。 - ninjagecko

1
为了做到这样的事情,您需要编写一个返回装饰器函数的函数。因此,在这种情况下(假设您想接受任意属性),您将编写以下内容
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函数更通用,您希望通过functools.wraps将参数传递给fn并保留其他信息,如函数名称:
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接