首页 > 解决方案 > 为什么 functools.update_wrapper 更新包装对象中的 __dict__ ?

问题描述

我遇到了一种特殊的行为functools.update_wrapper:它用被包装对象的覆盖对象覆盖了__dict__包装对象的 —— 这可能会阻碍它在嵌套装饰器时的使用。

作为一个简单的例子,假设我们正在编写一个将数据缓存在内存中的装饰器类和另一个将数据缓存到文件的装饰器类。下面的示例演示了这一点(我将示例简短并省略了所有缓存逻辑,但我希望它演示了这个问题):

import functools

class cached:
    cache_type = 'memory'
    def __init__(self, fcn):
        super().__init__()
        self.fcn = fcn
        functools.update_wrapper(self, fcn, updated=())

    def __call__(self, *args):
        print("Retrieving from", type(self).cache_type)
        return self.fcn(*args)

class diskcached(cached):
    cache_type = 'disk'

@cached
@diskcached
def expensive_function(what):
    print("expensive_function working on", what)

expensive_function("Expensive Calculation")

此示例按预期工作 - 它的输出是

Retrieving from memory
Retrieving from disk
expensive_function working on Expensive Calculation

然而,我花了很长时间才完成这项工作——起初,我没有在 functools.update_wrapper 调用中包含“updated=()”参数。但是当它被忽略时,嵌套装饰器不起作用 - 在这种情况下,输出是

Retrieving from memory
expensive_function working on Expensive Calculation

即外部装饰器直接调用最里面的包装函数。这样做的原因(我花了一段时间才理解)是将包装器的属性functools.update_wrapper更新为被包装参数的属性 - 这会使内部装饰器短路,除非添加参数。__dict____dict__updated=()

我的问题:这种行为是有意的 - 为什么?(Python 3.7.1)

标签: pythonpython-decoratorsfunctools

解决方案


使包装函数看起来像它包装的函数是update_wrapper, 并且包括__dict__条目。它不会取代__dict__; 它调用update

如果update_wrapper不这样做,那么如果一个装饰器在一个函数上设置属性,而另一个装饰器包装了修改后的函数:

@decorator_with_update_wrapper
@decorator_that_sets_attributes
def f(...):
    ...

包装函数不会设置属性,使其与查找这些属性的代码不兼容。


推荐阅读