python中的循环引用

时间:2017-05-10 10:03:05

标签: python reference cycle

我不确定python如何处理循环引用(引用循环)。我检查了一些答案并找到this

  

Python的标准引用计数机制不能释放循环,因此示例中的结构会泄漏   但是,补充垃圾收集工具默认启用,如果其组件不再可以从外部访问且它们没有__del__()方法,则应该能够释放该结构。

我想这意味着如果引用周期中没有任何实例可以在外部访问,那么它们都将被清除。这是真的吗? 另一方面,有一个包weakref,通常用于处理地图字典。我想,它存在的目的是避免参考周期 总之,python可以自动处理引用循环吗?如果可以,为什么我们必须使用weakref

3 个答案:

答案 0 :(得分:4)

如果循环中的对象没有自定义__del__方法,则不必担心引用周期,因为Python可以(并且将)以任何顺序销毁对象。

如果您的自定义方法确实有__del__方法,那么Python并不知道一个对象删除是否会影响另一个对象删除。说当一个对象被删除时,它会设置一些全局变量。所以,物体附着在身边。作为快速测试,您可以创建一个打印内容的__del__方法:

class Deletor(str):
    def __del__(self):
        print(self, 'has been deleted')

a = Deletor('a')  # refcount: 1
del a             # refcount: 0

输出:

a has been deleted

但如果你有这样的代码:

a = Deletor('a')  # refcount: 1
a.circular = a    # refcount: 2
del a             # refcount: 1

它不输出任何内容,因为Python无法安全删除a

这有两个解决方案。 weakref

# refcount:             a  b
a = Deletor('a')      # 1  0
b = Deletor('b')      # 1  1
b.a = a               # 2  1
a.b = weakref.ref(b)  # 2  1
del a                 # 1  1
del b                 # 1  0
# del b kills b.a     # 0  0

输出:

b has been deleted
a has been deleted

(请注意b在删除a

之前先删除的方法

您可以手动删除周期(如果可以跟踪它们):

# refcount          a  b
a = Deletor('a')  # 1  0
b = Deletor('b')  # 1  1
b.a = a           # 2  1
a.b = b           # 2  2
del b             # 2  1
print('del b')
del a.b           # 2  0
# b is deleted, now dead
# b.a now dead    # 1  0
print('del a.b')
del a             # 0  0
print('del a')

输出:

del b
b has been deleted
del a.b
a has been deleted
del a

请注意删除 b后删除a.b

答案 1 :(得分:2)

试着回答我们为什么会有弱引用子问题。

Weakrefs不仅会破坏循环引用,还会阻止不需要的非循环引用。

我最喜欢的例子是使用 Actions builder = new Actions(ngDriver); var elementToClick = ngDriver.FindElement(By.ClassName("dpcontract")); builder.MoveToElement(elementToClick, elementToClick.Size.Width - 1, 0) .ClickAndHold() .MoveByOffset(150, 0) .Release(); builder.Build().Perform(); 计算同时网络连接(负载测量的种类)。在此示例中,必须将每个新连接添加到WeakSet,但这是网络代码需要执行的唯一任务。连接可以由服务器,客户端或错误处理程序关闭,但这些例程都不负责从集合中删除连接,这是因为其他引用很弱。

答案 2 :(得分:0)

变量是内存引用。

 my_var=10 

此内容存储在其中一个内存插槽中。 my_var实际上引用了存储10的内存插槽的地址。如果您输入:

id(my_var) 

您将获得以10为基数的插槽的地址。 hex(id(my_var) )将给出地址的十六进制表示。

每当我们使用my_var时,Python内存管理器都会进入内存并获取值10。Python内存管理器还会跟踪此内存插槽的引用数。如果没有对该内存地址的引用,则python内存管理器将销毁该对象,并将该内存插槽用于新对象。

想象我们有两个类:

class A:
    def __init__(self):
        self.b = B(self)
        print('A: self: {0}, b:{1}'.format(hex(id(self)), hex(id(self.b))))

class B:
    def __init__(self, a):
        self.a = a
        print('B: self: {0}, a: {1}'.format(hex(id(self)), hex(id(self.a))))

当您定义类A的实例时:

   my_var = A()

您将获得此打印:(在您的系统中,您将拥有不同的地址)

B: self: 0x1fc1eae44e0, a: 0x1fc1eae4908
A: self: 0x1fc1eae4908, b:0x1fc1eae44e0

请注意参考文献。它们是循环引用。

注意:为了查看这些引用,您必须禁用垃圾收集器,否则它将自动删除它们。

  gc.disable()

当前my_var的引用计数为(0x1fc1eae4908)为2。my_var和classB引用此地址。如果我们更改my_var

   my_var= None

现在my_var没有指向相同的内存地址。现在(0x1fc1eae4908)的引用计数为1,因此该内存插槽未清除。

现在我们将出现内存泄漏,即不再清理不再需要的内存的时间。

垃圾收集器将自动识别循环引用中的内存泄漏并进行清理。但是,即使在循环引用中甚至有一个对象具有 destructor del ()),Garbage Collector也不知道对象的销毁顺序。因此,该对象被标记为无法收集,并且循环引用中的对象没有被清除,从而导致内存泄漏

weakref用于缓存。我认为python对于一切都有很好的文档。

这是weakref的参考:

weakref documentation