记一个python默认参数引用泄露问题

Posted by Shi Hai's Blog on February 23, 2023

一、问题背景

如下这段代码后,执行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={})的函数定义有关。为了确认cachecompute()函数中的引用计数量,将测试代码修改为如下所示:

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.

三、参考文献