In both situation

__del__

doesn't work. I understand that 'del obj' don't delete all references, but why/how/which references created?

Example 1:

    class Exp:
        def __init__(self):
            self.b = 1
            self.a = lambda :self.b

        def __del__(self):
            print('a')

    obj = Exp()
    obj = None
    del obj

Example 2:

class Exp:
        def __init__(self):
            self.b = 1
            self.a = self.test

        def test(self):
            return 5

        def __del__(self):
            print('a')

    obj = Exp()
    obj = None
    del obj

Dani AI

Generated

Short answer: both of your examples create a strong reference back to the instance, so the instance is part of a reference cycle and its __del__ is not run immediately. Assigning self.a = self.test stores a bound-method object whose __self__ points to the instance; self.a = lambda: self.b creates a closure (a function with __closure__ cells) that captures self. Those internal references keep the object alive until the cyclic GC handles it. (docs.python.org)

Why that matters in practice: CPython primarily uses reference counting, but it also has a cyclic collector for groups of objects that reference each other. Finalization of objects in cycles is handled specially (PEP 442 changed the semantics), so __del__ timing is implementation- and GC-dependent — it can run later, once the cycle is collected, and it’s not guaranteed at interpreter shutdown. That explains why obj = None; del obj in a short script may not print your message. (peps.python.org)

What to do instead (practical fixes you can apply right now):

  • Break the cycle explicitly (clear self.a before dropping the last external reference).
  • Store a weak reference instead of a bound method / closure (use weakref.WeakMethod for bound methods or weakref.ref inside your lambda).
  • Or register a cleanup callback with weakref.finalize rather than relying on __del__.

Example patterns (not the same as the code in the thread):

import weakref

# keep a weak ref to the bound method
self._weak_test = weakref.WeakMethod(self.test)
m = self._weak_test()
if m:
    m()   # call the recreated bound method

# or use a finalizer
self._finalizer = weakref.finalize(self, cleanup_func, arg1)

See the weakref docs for WeakMethod and finalize. (docs.python.org)

Notes tied to the discussion: was right that removing the attribute breaks the problem (you’re breaking the cycle); was right that collection can be delayed by the GC. For tests you can call gc.collect() to force a collection, but in production prefer explicit close/context managers or weakref.finalize instead of relying on __del__. (docs.python.org)

Recommended Answers

All 7 Replies

There is no need to do an explicit del obj. Python does a __del__ automatically as soon as you do obj = None. If you comment out the lambda line you will get what you expect. I can't explain why the lambda line screws things up. I hate lambdas and never use them. Typically a lambda involves a parameter which is used in the calculation of the returned value like

x = lambda a: a * 2

In your case you are using self.b for a reason I do not understand but which seems irrelevant to your example.

commented: I'm sorry, i forgot to remove line with "obj None". I use self.b just for example. And i either can't understatnd why this happen. +0

For more info see Python del

commented: it's not even a lambda.it's about assigning a class method to an attribute same class. Like in second example. +0

I’m no Python expert, but my understanding is that del is called when and if the object is deleted by the garbage collecter. This may be some time after the last reference is released by obj = None, definitely not “as soon as you do obj = None”.
Maybe the test program needs to wait until there is a garbage cleaner pass before declaring the del as m.i.a.

Also, making a new reference to self in the destructor may increase the reference count by 1 and thus make the object ineligible for garbage collection / destruction. I believe this is called “resurrection” and can have unpredictable or bizarre consequences.

The problem is the self.b in the lambda. If you first delete self.a then it works as expected. Try

class Exp:
    def __init__(self):
        print('__init__')
        self.b = 1
        self.a = lambda: self.b

    def __del__(self):
        print('__del__')

obj = Exp()
obj.a = None
obj = None
commented: well, yes, but what happen in second example? +0

I presume the reasoning is the same as that for copy/deep copy. If you have a complicated structure, making a simple copy does not duplicate the nested objects. Because the lambda creates a nested object reference, deleting the object does not delete the lambda reference so the del does not get triggered and the object will not be released by garbage collection. In my last example I deleted the embedded reference, then deleted the object and the del was then called.

The destructor is a special member function in C++ that is responsible for releasing any resources that were allocated by an object during its lifetime. The destructor is automatically called when an object goes out of scope or when it is explicitly destroyed using the "delete" keyword.

If the destructor doesn't seem to be working, there are a few possible reasons why this might be happening:

The object might not be getting destroyed: If the object is not going out of scope or is not being explicitly destroyed using the "delete" keyword, the destructor will not be called. In this case, you should check that the object is being properly managed and destroyed.

The destructor might not be defined correctly: If the destructor is not defined correctly, it may not be releasing the resources that were allocated by the object. Make sure that the destructor is releasing all resources properly, including any dynamically allocated memory.

The object might have been deleted twice: If an object is deleted twice, it can cause memory corruption and other problems. In this case, the destructor may not be working correctly because the object is already destroyed.

There may be other issues with the code: If the destructor is not working correctly, there may be other issues with the code that are causing the problem. Make sure that the code is properly structured and that all objects are being managed correctly.

The destructor is automatically called when an object goes out of scope

Let's distinguish between an object and a reference to it. An object can be garbage collected when there are no more references to it. You can have more than one reference to an object.

>>> a = [1,2,3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> del a
>>> b
[1, 2, 3]
>>> a = None
>>> b
[1, 2, 3]
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.