Python奇怪的装饰器

430 阅读2分钟

遇到一个内存泄露的问题,调试了很久发现是缓存装饰器的锅。

环境:python3.8

示例程序

有这样一个用作缓存的装饰器,示例代码如下:

# cache.py

import time
import random
import functools

class cache(object):
    """这是一个装饰器,我也是第一次看到这么写的装饰器"""

    def __init__(self, func):
        self.func = func
        self.data = {}

    def __call__(self, *args):
        if args in self.data:
            return self.data[args]
        value = self.func(*args)
        self._set_cache(value, *args)
        return value

    def _set_cache(self, value, *args):
        self.data[args] = value

    def __get__(self, obj, objtype):
        func = functools.partial(self.__call__, obj)
        return func

测试demo如下:

from .cache import cache

class Fish(object):

    def __init__(self):
        pass

    @cache
    def get(self, n):
        return "-".join([str(int(time.time())), str(n)])

fish = Fish()
print(fish.get(3))
time.sleep(1)
print(fish.get(3))
time.sleep(1)
print(fish.get(3))

输出结果:

1602759443-3
1602759443-3
1602759443-3

可以看到最后输出的结果是一样的,说明装饰器的缓存起作用了。

问题&原因

装饰器对象的创建是在创建Fish类get方法的时候,而不是在创建fish对象的时候。所以缓存在程序运行期间会一直存在,不会随着对象的删除而消失。在web程序中这会有问题,每次请求的数据都会被缓存存储,导致内存不断增大。

装饰器的特别之处

实际上装饰器在python解释器创建函数对象的时候就会执行,示例代码如下:

class dec1(object):

    def __init__(self, func):
        print("dec1")
        self.func = func

    def __call__(self, *args):
        print("call func...")
        return self.func(*args)

def dec2(func):
    print("dec2")
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@dec1
def f1():
    pass

@dec1
def f2():
    pass

@dec2
def f3():
    pass

if __name__ == "__main__":
    pass

输出结果:

dec1
dec1
dec2

虽然我们的程序中没有执行任何东西,但可以看到装饰器内的代码还是执行了。在创建f1/f2/f3 这些函数对象时,装饰器就被执行了。

好像有点不太好理解,欢迎评论交流!