【问题标题】:Python decorator to keep signature and user defined attribute用于保留签名和用户定义属性的 Python 装饰器
【发布时间】:2018-07-22 15:05:00
【问题描述】:

我有一个简单的装饰器my_decorator,它装饰了my_func

def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

@my_decorator
def my_func(x):
    print('hello %s'%x)

my_func._decorator_name_
'my_decorator'

直到这里一切正常,但我看不到函数的实际签名。

my_func?
Signature: my_func(*args, **kwargs)
Docstring: <no docstring>
File:      ~/<ipython-input-2-e4c91999ef66>
Type:      function

如果我用 python 的decorator.decorator 装饰我的装饰器,我可以看到我的函数的签名,但我不能拥有我定义的新属性。

import decorator

@decorator.decorator
def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

@my_decorator
def my_func(x):
    print('hello %s'%x)

my_func?
Signature: my_func(x)
Docstring: <no docstring>
File:      ~/<ipython-input-8-934f46134434>
Type:      function

my_func._decorator_name_
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-7e3ef4ebfc8b> in <module>()
----> 1 my_func._decorator_name_

AttributeError: 'function' object has no attribute '_decorator_name_'

如何在 python2.7 中同时拥有这两者?

【问题讨论】:

标签: python python-2.7 ipython python-decorators


【解决方案1】:

正如其他人指出的那样,您似乎没有正确使用decorator

或者,您可以使用我的库 makefun 创建您的签名保留包装器,它依赖于与 decorator 相同的技巧来保留签名,但更专注于动态函数创建并且更通用(您可以更改签名):

from makefun import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

您可以检查它是否按预期工作:

@my_decorator
def my_func(x):
    """my function"""
    print('hello %s' % x)

assert my_func._decorator_name_ == 'my_decorator'
help(my_func)

对于它的价值,如果您希望稍后为您的装饰器添加可选参数而不会使代码看起来更复杂,请查看decopatch。例如,如果您希望 _decorator_name_ 成为装饰器的可选参数:

from decopatch import function_decorator, DECORATED
from makefun import wraps

@function_decorator
def my_decorator(name='my_decorator', func=DECORATED):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    wrapper._decorator_name_ = name
    return wrapper

【讨论】:

    【解决方案2】:

    对于 Python 3,在标准库中使用 functools.wraps

    from functools import wraps
    
    def my_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        wrapper._decorator_name_ = 'my_decorator'
        return wrapper
    
    @my_decorator
    def my_func(x):
        print('hello %s'%x)
    
    print(my_func._decorator_name_)
    

    【讨论】:

    • @bruce_wayne 在 python3.6 中工作
    • 从 python 3.7 或更低版本开始,functools.wraps 实际上并不保留签名。它似乎保留了它,但这是因为 inspect.signature 遵循它设置的 __wrapped__ 魔法属性。它有两个缺点,主要的一个是用户在使用带有无效参数的包装器时没有得到TypeError,它仍然会被执行,直到它调用包装的func(最终会引发错误)。见stackoverflow.com/questions/308999/what-does-functools-wraps-do/…
    【解决方案3】:

    如果您想以某种方式改变函数的行为,您只需要定义一个wrapper 函数。因此,如果您真的只想在函数中添加一些属性而不改变其行为,您可以简单地执行以下操作。

    def my_decorator(func):
        func._decorator_name_ = 'my_decorator'
        return func
    

    在需要wrapper 的更复杂的情况下,我建议遵循accepted answer for this question,它解释了如何创建一个行为与另一个相同但具有自定义签名的函数。使用inspect.getargspec,您可以从my_func 恢复签名并将其转置到您的wrapper

    【讨论】:

      【解决方案4】:

      @decorator.decorator 返回一个函数,该函数将另一个函数作为输入。在您的情况下,您需要返回函数的属性。

      要让它在 Python 2.7 上运行,您只需要进行一些调整

      import decorator
      
      def my_dec2(func):
          @decorator.decorator
          def my_decorator(func, *args, **kwargs):
              print("this was called")
              return func(*args, **kwargs)
      
          test = my_decorator(func)
          test._decorator_name_ = "my_decorator"
          return test
      
      @my_dec2
      def my_func(x):
          print('hello %s'%x)
      
      
      my_func(2)
      print(my_func._decorator_name_)
      

      然后当你测试它的时候

      In [1]: my_func?
      Signature: my_func(x)
      Docstring: <no docstring>
      File:      ~/Desktop/payu/projects/decotest/decos.py
      Type:      function
      
      In [2]: my_func._decorator_name_
      Out[2]: 'my_decorator'
      

      【讨论】:

      • 这完全适合 Python 2,谢谢!装饰器模块也破坏了 func_globals,但我也能够通过从原始 func 复制它们来保留它们。
      猜你喜欢
      • 1970-01-01
      • 2017-07-10
      • 2019-03-11
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 2016-09-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多