【问题标题】:Arguments for Python decorators [duplicate]Python 装饰器的参数 [重复]
【发布时间】:2013-07-19 07:01:30
【问题描述】:

所以下面...

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

任何建议都会很棒!

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    用另一个函数包装你的函数:

    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
    

    我把如何构建字符串留给读者作为练习。

    请注意,装饰器表达式的结构是@ &lt;callable object of one parameter, w&gt; &lt;declaration of callable object, f&gt;。它的效果是f = w(f)。因此,w(装饰器)必须返回与f 相同类型的可调用对象。

    @makebold(foo)def bar(x):pass中,表达式makebold(foo)是装饰器——也就是说,装饰器的最终效果是bar = makebold(foo)(bar),所以bar最终持有wrapped

    functools.wraps 的目的是修复修饰函数的属性,以便将元数据(例如名称和文档字符串)从参数函数复制到包装函数,从而使整个包装过程透明。

    【讨论】:

    • 在那里推functools.wraps 可能不会有什么坏处...
    • @JonClements 好点。
    • 你能解释一下包括 functools 装饰器的装饰器层吗?
    • @Stephan functools 装饰器有 documentation 足以解释它。也就是说,解释为什么解决方案的结构必须这样看可能是个好主意。
    • 一个返回函数的函数,它接受一个函数并返回一个函数。我称之为函数式编程!
    【解决方案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:我个人对partial 既爱又恨,因为我过去曾遇到过问题。我觉得它有点笨拙并破坏了方法签名,尽管柯里化很好。尽管@wraps 的存在据称是为了保留函数的名称和文档字符串,但如果我想要的话,恕我直言,最好使用一个“真正的”装饰器框架来让一切都正确。不必要的装饰器也会产生大量开销,我曾经试图忽略但失败了。一旦制作了一个重写函数并将其重新编译成“应该”的框架。
    • @millimoose:[继续] 但也许你是对的,我应该使用它,没有其他原因可能是向前兼容性,也许用另一个可能被换掉的 from myfuture import wraps 替换它做其他事情。仍然不确定我是否应该在示例中使用它,因为这可能会增加所需的脑力来弄清楚正在做什么,因为它基本上什么都不做(我使用它已经有一段时间了,也许我忽略了 *args 或 * *千瓦方便吗?)。不过还是谢谢你的建议。
    • 从 Python 3.3 开始,functools.wraps 支持将函数签名保留为使用inspect.signature() 可见。 (不幸的是不是getargspec(),由于某种原因,这两种机制没有统一。)所以你想要的“'真正的'装饰器框架'实际上是wraps()。(我承认我不知道这个从在我的脑海中,我突然想到 Python 开发团队中的某个人可能想到了这一点 - 事实证明他们做到了。)
    • @millimoose:哦,太好了,谢谢。当性能不是问题时,我可能会使用它。
    【解决方案3】:

    为了做这样的事情,你需要一个返回装饰器函数的函数。所以在这种情况下(假设你想接受任意属性),你会写

    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,并使用functools.wraps 保留函数名称等其他信息:

    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
    

    【讨论】:

      猜你喜欢
      • 2023-03-26
      • 2014-10-14
      • 2014-07-21
      • 2020-04-13
      • 2012-02-09
      • 2011-11-21
      • 1970-01-01
      • 2015-06-11
      相关资源
      最近更新 更多