Skip to content Skip to sidebar Skip to footer

How To Ensure The __del__ Function Is Called On A Python Class As Is Commonly (but Incorrectly) Expected?

I understand that the __del__ function of a Python class is not treated in the way that many people might expect: as a destructor. I also understand that there are more 'pythonic'

Solution 1:

If you understand all that, why not do it in the Pythonic way? Compare another class where cleanup is important: tempfile.TemporaryDirectory.

with TemporaryDirectory() as tmp:
    # ...
# tmp is deleted

def foo():
    tmp = TemporaryDirectory()
foo()
# tmp is deleted

How do they do this? Here's the relevant bit:

import weakref
class Foo():
    def __init__(self, name):
        self.name = name
        self._finalizer = weakref.finalize(self, self._cleanup, self.name)
        print("%s reporting for duty!" % name)

    @classmethod
    def _cleanup(cls, name):
        print("%s feels forgotten! Bye!" % name)

    def cleanup(self):
        if self._finalizer.detach():
            print("%s told to go away! Bye!" % self.name)

def foo():
    print("Calling Arnold")
    tmpfoo = Foo("Arnold")
    print("Finishing with Arnold")

foo()
# => Calling Arnold
# => Arnold reporting for duty
# => Finishing with Arnold
# => Arnold feels forgotten. Bye!

def bar():
    print("Calling Rocky")
    tmpbar = Foo("Rocky")
    tmpbar.cleanup()
    print("Finishing with Rocky")

bar()
# => Calling Rocky
# => Rocky reporting for duty!
# => Rocky told to go away! Bye!
# => Finishing with Rocky

weakref.finalize will trigger _cleanup when the object is garbage-collected, or at the end of the program if it's still around. We can keep the finaliser around so that we can explicitly kill the object (using detach) and mark it as dead so the finaliser is not called (when we want to manually handle the cleanup).

If you want to support the context usage with with, it is trivial to add __enter__ and __exit__ methods, just invoke cleanup in __exit__ ("manual cleanup" as discussed above).


Solution 2:

This is a pattern I have been employing that achieves this using the atexit python module.

class Demo(object):
    def __init__(self, *args, **kwargs):
        import atexit
        atexit.register(self.__del__)

    def __del__(self):
        print("__del__ being called!")

t1 = Demo()
t2 = Demo()

quit()

When pasted into a python command prompt, this is the total output:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> class Demo(object):
...     def __init__(self, *args, **kwargs):
...         import atexit
...         atexit.register(self.__del__)
...
...     def __del__(self):
...         print("__del__ being called!")
...
>>> t1 = Demo()
>>> t2 = Demo()
>>>
>>> quit()
__del__ being called!
__del__ being called!

Post a Comment for "How To Ensure The __del__ Function Is Called On A Python Class As Is Commonly (but Incorrectly) Expected?"