Python 装饰器是如何工作的?

How does Python decorator work under the hood?

我对 Python 中装饰器内部发生的事情有一些疑问。

  1. 考虑将总和保存在日志文件中的代码:
def logger(func):
    def wrapped(*args, **kwargs):
        result = func(*args, **kwargs)
        with open('log.txt', 'w') as f:
            f.write(str(result))
            
        return result
    return wrapped        
        
@logger
def summator(numlist):
    return sum(numlist)

print(f"Summator: {summator([1, 2, 3, 4, 5])}")

当我 运行 summator([1, 2, 3, 4, 5, 6]) 时它是如何工作的?我假设 logger 发送 wrapped 内的 summator([1, 2, 3, 4, 5, 6]) 函数实例,其中执行 summator 函数并且它的结果以某种方式被修改。但是 def logger(func) 对我来说很奇怪:这是否意味着它需要 func 并附加 func 个参数?我看到 logger 本身不接受 * args, ** kwargs,只有 wrapped 接受...

  1. 考虑类似的代码:
def logger(filename):
    def decorator(func):
        def wrapped(*args, **kwargs):
            result = func(*args, **kwargs)
            with open(filename, 'w') as f:
                f.write(str(result))
            
            return result
        return wrapped
    return decorator

        
@logger('new_log.txt')
def summator(numlist):
    return sum(numlist)

decorator如何得到func?它由 logger 包装,只接受文件名。

这是我对装饰器工作原理的简单解释。

你要装饰一个函数,在这里summator并基本上在不触及它的主体的情况下向它添加一些功能。

表达式:

@logger
def summator(numlist):
    return sum(numlist)

相当于:

def summator(numlist):
    return sum(numlist)

summator = logger(summator)

@只是一个语法糖。

发生了什么事?
查看 logger 函数。它有什么作用 ?它需要一个 function 和 returns 另一个 function(此处命名为 wrapped)。不是那个函数的结果,只是函数本身。

然后您将调用 logger(summator) 返回函数 分配给名为 summator 的符号。从现在开始,无论何时调用 summator,都是在调用 logger 返回的函数。您正在呼叫 wrapped.

wrapped里面有一个func。它是什么 ?这不是您在上一段中传递给 logger 的原始 summator 函数吗?是的。

问:wrapped函数怎么访问呢?它不是它的局部变量!
A:因为 wrapped 是一个闭包并且可以访问封闭作用域的局部变量。 read more here

所以wrapped调用func,存储结果,将其写入文件然后returns它。

注意:我们通常用这个签名定义我们的wrapped函数:def wrapped(*args, **kwargs):因为我们想自由地传递所有参数(包括位置和关键字参数)到 func 函数。归根结底 func 和原来的 summator 是同一个对象。在你的情况下你可以这样写:

def logger(func):
    def wrapped(lst):       # <--------
        result = func(lst)  # <--------
        with open('log.txt', 'w') as f:
            f.write(str(result))

        return result
    return wrapped

你的第二个问题是指所谓的装饰器工厂。它是 returns 实际装饰器的函数。 问:我们什么时候使用它? A: 当我们想要从装饰器工厂到装饰器或其内部函数的附加参数时。 (在这里,您将 filename 作为参数传递给了装饰器工厂,但在 wrapped 函数中使用了它。)

故事是一样的,它只是我们装饰器之上的另一层。该语法糖的等价物是:

def summator(numlist):
    return sum(numlist)

summator = logger('new_log.txt')(summator)

# In two steps, it would be:
# decorator = logger('new_log.txt')
# summator = decorator(summator)

注意:在第一个问题中,name logger 是实际的装饰器(因为它使用 summator 函数进行装饰)但是在第二个问题中它不是。它现在是一个装饰工厂。

wrapped 访问 filename 的方式与我之前提到的访问 func 的方式相同。

了解更多信息 here