一、问题背景
如下这段代码后,执行compute()
函数前后,引用计数居然多了3,也就意味泄露了3个引用。
cache={}
会让cache
引用变量的引用计数加2,另外还有1个引用计数的增加以及怎么产生引用泄露的呢?
import sys
class A:
pass
def compute(cache={}):
cache[0] = dict()
x = A()
print(sys.gettotalrefcount())
compute()
print(sys.gettotalrefcount())
需要注意的是,sys.gettotalrefcount()
不是python3默认的函数,需要用with-pydebug
重新编译python解释器才会有。
二、问题分析
如果将上述的测试代码微调成下方的代码所示,则不会有引用泄露问题。
import sys
class A:
pass
def compute():
# 微调这里的代码
cache = dict()
cache[0] = dict()
x = A()
print(sys.gettotalrefcount())
compute()
print(sys.gettotalrefcount())
这个测试示例代码说明引用泄露和compute(cache={})
的函数定义有关。为了确认cache
在compute()
函数中的引用计数量,将测试代码修改为如下所示:
class A:
pass
def compute(cache={}):
print("Calling compute(), the refcount of cache is: ", sys.getrefcount(cache))
print("Calling compute(), the id of cache is: ", id(cache))
print("Before calling compute(), the total refcount: ", sys.gettotalrefcount())
compute()
print("After calling compute(), the id of cache is: ", id(compute.__defaults__[0]))
compute()
print("After calling compute() again, the total refcount: ", sys.gettotalrefcount())
print("After calling compute() again, the id of cache is: ", id(compute.__defaults__[0]))
输出结果如下所示,这说明了cache
的引用变量就是3。compute(cahce={})
函数使得cache
引用计数变2,另外函数也在__defaults__
中引用了cache
指向的dict
对象。
Before calling compute(), the total refcount: 47170
Calling compute(), the refcount of cache is: 3
Calling compute(), the id of cache is: 139992793636816
After calling compute(), the id of cache is: 139992793636816
Calling compute(), the refcount of cache is: 3
Calling compute(), the id of cache is: 139992793636816
After calling compute() again, the total refcount: 47170
After calling compute() again, the id of cache is: 139992793636816
此外,从这个测试代码执行可以推断出一个结论:当函数的默认参数是一个可变对象并且无入参时,默认参数只会初始化一次。 实际官网有个明确的定义描述:Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.
三、参考文献
- 一个 Python Bug 干倒了估值 1.6 亿美元的公司
- 讨论PYTHON函数默认参数的坑
- 常见陷阱
- Why disable the garbage collector?
- Python程序调试分析大杀器
- Python进程死循环定位