Python GC机制处理对象方法重写导致的循环引用解决

在编写mod的过程中,我常发现一个内存泄漏警告,比如“职业选手”这个mod,一开始我的写法是这样的

class careerSpaz(bs.PlayerSpaz):
    ...
    def changeCareer(type):
        ...
        if(type == 'boxer'):
            self.dropBomb = self.boxerNoBomb
        ...

然后运行后有时可以看到报垃圾回收无法处理这个对象<career_choicer.careerPlayerSpaz object at 0x7effa37e7490>
经过多次调试研究,发现只有当self.dropBomb = self.boxerNoBomb这句被执行才会报这样的错误,如果玩家并没有切换职业,没有执行这条语句,则可以正常垃圾回收。

开启Python gc模块的DEBUG_LEAK标志位,可以看到该对象无法回收的原因出在<bound method careerPlayerSpaz.boxerNoBomb of <career_choicer.careerPlayerSpaz object at 0x7effa37e7490>>

1. 错误模型

根据网上查找的资料,大致可以确定错误模型如下


>>> class A(object):
    def __del__(self):
        print("DEL")
    def a(self):
        pass

>>> a = A()
>>> del a
DEL
# 没问题
>>> a = A()
>>> a.a = a.a
>>> del a
# 没有调用__del__函数,无法完成GC

具体原因如下:

you effectively "cache" the act of binding the method. As the bound method has a reference to the object (obviously, as it has to pass self to the function) this creates a circular reference.

2. 解决方法

使用修饰器函数

import weakref

def weakmethod(method):
    cls = method.im_class
    func = method.im_func
    instance_ref = weakref.ref(method.im_self)
    del method

    def inner(*args, **kwargs):
        instance = instance_ref()

        if instance is None:
            raise ValueError("Cannot call weak method with dead instance")

        return func.__get__(instance, cls)(*args, **kwargs)

    return inner

赋值采用a.a = weakmethod(a.a)即可

1 条评论

  1. 233

    为何发表不了评论

发表评论