Python Forum

Full Version: can someone explain this __del__ behaviour?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I came across something interesting that I can't explain. Perhaps someone here can. When the following code is run with the lambda commented out it produces the output I expect to see.

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

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

    obj = Exp()
    obj = None
Output

    __init__
    __del__
But with the lambda uncommented

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

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

    obj = Exp()
    obj = None
I do not see that __del__ is executed.

    __init__
As I understand it, __del__ executes when the reference count for an object reaches zero so I can only assume that somehow the lambda line is doing something odd. But if I modify the lambda to

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

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

    obj = Exp()
    obj = None

I get

    __init__
    __del__
I'm not a fan of lambdas, but typically a lambda specifies a parameter and something that operates on that parameter like

    self.a = lambda x: x**2


so perhaps specifying self.b in the lambda expression is causing a problem with the reference counter.
A lambda expression like "lambda: self.b" creates a closure. The object self is referenced by the closure. You can see that here:
class Exp:
    def __init__(self):
        self.a = 1
        self.b = lambda: self.a

    def __str__(self):
        return f"a: {self.a}, b:{self.b()}"

obj = Exp()
print(obj)
obj.a = 2
print(obj)
a: 1, b:1
a: 2, b:2
How does the lambda expression know the value of obj.a has changed to 2? It knows because it created a closure that not only knows what code to execute (return self.a), but contains a reference to the object self. When you reassign obj = None this does not remove the reference in the lambda expression. To do that:
obj = Exp()
obj.b = None
obj = None
Now the reference count goes to zero and the object is deleted.