如何在Python中“事后”向函数附加装饰器?

34

我理解的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

现在,如何将修饰器附加到在其他地方定义的函数上,最好保留原始函数的名称和文档字符串(就像 functools.wraps 一样)?例如,我从 Python 的 math 模块中导入 sqrt() 函数,并想要对其进行修饰,我该怎么做?

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

那么在类内之后如何装饰方法?对于装饰类本身呢?

1个回答

36
你将sqrt导入到你的模块中,只需在你自己的全局命名空间中应用装饰器即可:
sqrt = print_args_decor(sqrt)

这将在你的模块命名空间中设置名称为sqrt的结果。并不要求sqrt最初是在该模块中定义的。

由装饰器使用functools.wraps()装饰器来保留函数元数据,例如名称和文档字符串。

在这方面,装饰类与函数没有任何区别:

ClassName = decorator(ClassName)

在Python 2中,对于方法 (methods),你需要小心地获取原始的未绑定函数。最简单的方法是使用method.__func__属性:
try:
    # Python 2
    ClassName.function_name = decorator(ClassName.function_name.__func__)
except AttributeError:
    # Python 3
    ClassName.function_name = decorator(ClassName.function_name)

我已将上述内容用try...except包装起来,以使该模式适用于各个Python版本。另一种方法是从类__dict__中获取函数对象,以避免描述符协议的干扰:
ClassName.function_name = decorator(ClassName.__dict__['function_name'])

看起来足够简单,谢谢。你能添加一个在类内和类中方法上装饰的例子吗? - con-f-use
1
@con-f-use:装饰器只是一个可调用对象。如你所述,@decorator 语法只是将被修饰的对象传递给装饰器可调用对象,并用结果替换名称。因此,对于一个类来说,这只是 ClassName = decorator(ClassName) - Martijn Pieters
2
@con-f-use:现有的方法有点棘手,因为装饰器应用于函数对象,在它成为类的一部分之前。您可以使用__func__属性获取原始函数:ClassName.function_name = decorator(ClassName.function_name.__func__) - Martijn Pieters

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