如何在python中"将事后"附加到一个函数的装饰器?

con*_*use 9 python decorator python-2.7 python-decorators

我理解python中函数的装饰器(我可能错了)的方法是,它们应该添加副作用并修改函数的返回值.现在装饰器被添加到要装饰的函数的函数定义之上或通过赋值.这是一个小例子:

def print_args_decor(function):
    def wrapper(*args, **kwargs):
        print 'Arguments:', args, kwargs         # Added side-effect
        return function(*args, **kwargs)*5       # Modified return value
    return wrapper

@print_args_decor
def do_stuff(strg, n=10):
    """Repeats strg a few times."""
    return strg * n

new_decorated_func = print_args_decor(do_stuff)  # Decoration by assignment

print do_stuff('a', 2) # Output: aaaaaaaaaa
Run Code Online (Sandbox Code Playgroud)

现在,如何将装饰器附加到其他地方定义的函数,理想情况下保留原始函数的名称和文档字符串(如同functools.wraps)?例如,我sqrt()从Python的数学模块导入函数,并想要装饰它,我该怎么做呢?

from functools import wraps
from math import sqrt

def print_args_decor(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print 'Arguments:', args, kwargs         # Added side-effect
        return function(*args, **kwargs)*5       # Modified return value
    return wrapper

# Decorate the sqrt() function from math module somehow
@print_args_decor #???
sqrt #???

print sqrt(9)   
# Output: 
# Arguments: ([9],) {}
# 15                   # <--- sqrt(9)*5
Run Code Online (Sandbox Code Playgroud)

事后在类中装饰方法怎么样?如何自己装饰课程?

Mar*_*ers 17

您导入sqrt到模块中,只需将装饰器应用于您自己的全局命名空间:

sqrt = print_args_decor(sqrt)
Run Code Online (Sandbox Code Playgroud)

这会将sqrt模块命名空间中的名称设置为装饰器的结果.sqrt最初在此模块中没有要求.

装饰器可以使用functools.wraps()装饰器来保存函数元数据,例如name和docstring.

在这方面装饰课程没有什么不同:

ClassName = decorator(ClassName)
Run Code Online (Sandbox Code Playgroud)

在Python 2上,对于方法,您需要小心抓取原始的未绑定函数; 最简单的方法是使用method.__func__属性:

try:
    # Python 2
    ClassName.function_name = decorator(ClassName.function_name.__func__)
except AttributeError:
    # Python 3
    ClassName.function_name = decorator(ClassName.function_name)
Run Code Online (Sandbox Code Playgroud)

我已将上述内容包装在一起,try...except以使模式适用于Python版本.另一种方法是从类中获取函数对象,__dict__以避免描述符协议被踢入:

ClassName.function_name = decorator(ClassName.__dict__['function_name'])
Run Code Online (Sandbox Code Playgroud)

  • @ con-f-use:现有方法有点棘手,因为装饰器在成为类的一部分之前应用于*function对象*.您可以使用`__func__`属性获取原始函数:`ClassName.function_name = decorator(ClassName.function_name .__ func __)`. (2认同)