del self vs self.__del__() - and what is the proper way to cleanup in python?
Asked Answered
F

1

7

I have a python script that contains a class.

This class has a __del__ method for usual cleanup. If I delete the class during the normal execution of the script, I want some cleanup to be done.

I also have a signal handler, that does some unusual cleanup. If the script was in the middle of executing something else when it received the signal, it needs to do some extra cleanup, followed by the regular cleanup.

I noticed there is a difference between doing del self and self.__del__().

Namely, self.__del__() calls the del method twice.

Can you please explain why this happens?

This is a mockup script to illustrate the issue:

import sys
import signal
from optparse import OptionParser


class TestClass(object):
    def __init__(self,
                 del_self):
        self.del_self = del_self

        print "__init__ with del_self = %s" % self.del_self

    def __del__(self):
        print "Now performing usual cleanup."

    def signal_handler(self, arg_1, arg_2):
        print "Received signal. Now performing unusual cleanup."

        # Unusual cleanup

        print "Did unusual cleanup"

        if self.del_self:
            print("Doing del self")
            del self
        else:
            print("Doing self.__del__()")
            self.__del__()

        sys.exit(0)


if __name__ == '__main__':
    arguments = sys.argv[1:]
    parse = OptionParser("Test.")
    parse.add_option(
        "-d",
        "--delself",
        help="Set del self.",
        type="int",
        default=1
    )

    (options, _) = parse.parse_args()

    print "Options: %s" % options

    tc = TestClass(del_self=options.delself)

    signal.signal(signal.SIGQUIT, tc.signal_handler)

    while True:
        pass

If I ran the script with:

python deltest.py --delself 1

And issue killall -3 python I get:

Received signal. Now performing unusual cleanup.
Did unusual cleanup
Doing del self
Now performing usual cleanup.

Where as, if I do python deltest.py --delself 0 again followed by the kill signal, i get:

Received signal. Now performing unusual cleanup.
Did unusual cleanup
Doing self.__del__()
Now performing usual cleanup.
Now performing usual cleanup.

Why is the __del__ method executed twice in this case?

Thanks!

Faso answered 20/1, 2017 at 14:15 Comment(0)
E
22

del self does almost nothing -- it only deletes the local variable that is named self. But as that isn't the last reference to the instance (whatever called this method also still has a reference, at least) the object will continue to exist.

Calling __del__ manually also achieves nothing except for running that method; it doesn't delete anything.

What does work is removing the last reference to an object: in this case, del tc inside the main method would have remove that last reference, as far as I can see. Then the object would have been garbage collected, and then Python calls __del__.

So that's why __del__ happens twice: one time when you call it manually as a normal function, and one time when the object is garbage collected when the program ends.

Enamour answered 20/1, 2017 at 14:24 Comment(7)
@random_person_123: yes, this is explained in details hereAbeyta
So, just to make sure that I understand correctly, since del is called when the garbage collection kicks in, I don't have to call it in my signal handler, because it is called automatically when the instance is garbage collected. Is this correct?Faso
@random_person_123: Yes, you never call it yourself, it's automatically called when the object is garbage collected. Note that officially Python doesn't guarantee when your object will be garbage collected, although CPython usually does that when the last reference is gone.Enamour
@RemcoGerlich, Ok, that makes sense. In the case of a SIGQUIT signal, are the references set to 0? What I'm asking actually is, how can I guarantee my usual cleanup is performed when the signal handler runs, without code duplication.Faso
It's not guaranteed to run when the interpreter exits. So if your __del__ is written in such a way that it's safe to call it multiple times, it might be a good idea to call it yourself before calling sys.exit.Enamour
Ok, that makes sense. I'll refactor my code with that in mind. Thanks for the tips!Faso
See docs.python.org/3.6/reference/datamodel.html#object.__del__ (think it's mostly the same in Python 2).Enamour

© 2022 - 2024 — McMap. All rights reserved.