遇到一个内存泄露的问题,调试了很久发现是缓存装饰器的锅。
环境: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 这些函数对象时,装饰器就被执行了。
好像有点不太好理解,欢迎评论交流!