Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.4k
Description
Bug report
Bug description:
When instantiating objects through a method with functools.cached_property decorator, sometimes the objects won't be cleaned by the garbage collector. More specifically, when the method is called by iterating a list of objects. It is easier to understand from the reproduction code. This does not happen, when the class object is being cached (some_other_method). Additionally, it does not appear, when the instance in the list/dictionary is being directly accessed (third run), which means one or the other must be an unintended side-effect (that the direct access gets garbage collected or the iterative access does not). In the final run, trying to deleted the lists does not remove the SomeOtherClass instances. The original SomeClass objects do not get removed either. (The final del is to test, if deleting the dictionaries triggers the garbage collector somehow)
Code to reproduce (objgraph has to be installed; can be done withpip install objgraph
):
fromfunctoolsimportcached_propertyimportobjgraphimportgcclassSomeOtherClass:passclassSomeClass:@cached_propertydefsome_method(self):returnSomeOtherClass()@cached_propertydefsome_other_method(self):returnSomeOtherClassprint("First run:")e1_list= {0:SomeClass()}fore1ine1_list.values():e1.some_methode1_list.clear()gc.collect()print("SomeClass:",objgraph.by_type('SomeClass'))print("SomeOtherClass:",objgraph.by_type('SomeOtherClass'))print()print("Second run:")e2_list= {0:SomeClass()}fore2ine2_list.values():e2.some_methode2_list.clear()gc.collect()print("SomeClass:",objgraph.by_type('SomeClass'))print("SomeOtherClass:",objgraph.by_type('SomeOtherClass'))print()print("Third run (no loop):")e3_list= {0:SomeClass()}e3_list[0].some_methode3_list.clear()gc.collect()print("SomeClass:",objgraph.by_type('SomeClass'))print("SomeOtherClass:",objgraph.by_type('SomeOtherClass'))print()print("Fourth run (some_other_method):")e4_list= {0:SomeClass()}fore4ine4_list.values():e4.some_other_method()e4_list.clear()gc.collect()print("SomeClass:",objgraph.by_type('SomeClass'))print("SomeOtherClass:",objgraph.by_type('SomeOtherClass'))print()print("Fifth run (Trying to clean up using del):")dele1_listdele2_listdele3_listdele4_listgc.collect()print("SomeClass:",objgraph.by_type('SomeClass'))print("SomeOtherClass:",objgraph.by_type('SomeOtherClass'))
Output:
First run:SomeClass: [<__main__.SomeClass object at 0x7153db48d6a0>]SomeOtherClass: [<__main__.SomeOtherClass object at 0x7153db48f0e0>]Second run:SomeClass: [<__main__.SomeClass object at 0x7153db48d6a0>, <__main__.SomeClass object at 0x7153db2f4690>]SomeOtherClass: [<__main__.SomeOtherClass object at 0x7153db48f0e0>, <__main__.SomeOtherClass object at 0x7153db2f47d0>]Third run (no loop):SomeClass: [<__main__.SomeClass object at 0x7153db48d6a0>, <__main__.SomeClass object at 0x7153db2f4690>]SomeOtherClass: [<__main__.SomeOtherClass object at 0x7153db48f0e0>, <__main__.SomeOtherClass object at 0x7153db2f47d0>]Fourth run (some_other_method):SomeClass: [<__main__.SomeClass object at 0x7153db48d6a0>, <__main__.SomeClass object at 0x7153db2f4690>, <__main__.SomeClass object at 0x7153db4d09d0>]SomeOtherClass: [<__main__.SomeOtherClass object at 0x7153db48f0e0>, <__main__.SomeOtherClass object at 0x7153db2f47d0>]Fifth run (Trying to clean up using del):SomeClass: [<__main__.SomeClass object at 0x7153db48d6a0>, <__main__.SomeClass object at 0x7153db2f4690>, <__main__.SomeClass object at 0x7153db4d09d0>]SomeOtherClass: [<__main__.SomeOtherClass object at 0x7153db48f0e0>, <__main__.SomeOtherClass object at 0x7153db2f47d0>]
CPython versions tested on:
CPython main branch, 3.15, 3.14, 3.13, 3.10
Operating systems tested on:
Linux