Is overriding __del__() the best choice here?
Asked Answered
T

2

9

I am trying to figure out the best way to remove something, preferably without having to write in a lot of code.

In my project I am simulating chemical compounds - I have Element instances bonded to other Element instances via a Bond instance. In chemistry bonds often break, and I'd like to have a clean way to do that. My current method is something like as follows

# aBond is some Bond instance
#
# all Element instances have a 'bondList' of all bonds they are part of
# they also have a method 'removeBond(someBond)' that removes a given bond 
# from that bondList
element1.removeBond(aBond)
element2.removeBond(aBond)
del aBond

I want to do something like

aBond.breakBond()

class Bond():
    def breakBond(self):
        self.start.removeBond(self) # refers to the first part of the Bond 
        self.end.removeBond(self) # refers to the second part of the Bond 
        del self

Alternately, something like this would be fine

del aBond

class Bond():
    def __del__(self):
        self.start.removeBond(self) # refers to the first part of the Bond 
        self.end.removeBond(self) # refers to the second part of the Bond 
        del self

Is any one of these ways of doing it preferable to the others, or is there some other way of doing this that I'm overlooking?

Ternopol answered 13/2, 2014 at 22:8 Comment(0)
X
6

Python uses garbage collection to manager memory, which means you do not have to delete anything. This class is fine:

class Bond():
    def breakBond(self):
        self.start.removeBond(self)
        self.end.removeBond(self)

note that del does not delete anything from memory! It simply removes a reference to an object, but objects can have more than one reference:

>>> some_list = [1,2,3]
>>> b = some_list
>>> del b   # destroys the list?
>>> b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>> some_list   # list is still there!
[1, 2, 3]
>>> c = some_list
>>> del some_list
>>> c        # list is still there!
[1, 2, 3]
>>> del c

After the last del c the interpreter can deallocate the list. In CPython the deallocation will be done immediately (in this simple case), however in other implementation of the language the interpreter might not deallocate the list immediately.

Also note that __del__'s documentation cites this fact. Furthermore it is a really low-level method which you don't need 99.9% of the time, so it certainly isn't the right way to handle your situation.

Xanthe answered 13/2, 2014 at 22:14 Comment(3)
Thanks, this is basically what I was expecting I would need to do. Thank you (also) for clarifying how del works, I've never really understood thatTernopol
One common use case for overriding __del__ is for terminating threads or processes managed by the class. They won't be terminated by the garbage collector on their own.Bailable
@LeFrite Yes, but if you are relying on __del__ to be called you are doing it wrong. It can be a holy mary, but if you have code handling resources you should properly and explcitly handle their release. __del__ can help you in case someone calls sys.exit (but not os._exit!) and even then you have about a 50% chance that the __del__ is called (see e.g. here: https://mcmap.net/q/137802/-__del__-on-exit-behavior)Xanthe
D
1

The first way is pretty tedious and error-prone. The second is fine, but the del self in Bond.breakBond is completely and utterly pointless (more on this below). The third is hacky, unreliable, and in this specific case not working at all (due to the circular reference between Bond and Elements, __del__ is never invoked unless you upgrade to Python 3.4, but even then it remains hacky and unreliable).

del name only removes the local name, it does not call __del__ or otherwise affect the object. It has absolutely no effect on memory management, except possibly allowing earlier garbage collection if name was the last (reachable) reference.

You should do this:

aBond.breakBond()

class Bond():
    def breakBond(self):
        self.start.removeBond(self) # refers to the first part of the Bond 
        self.end.removeBond(self) # refers to the second part of the Bond 
Darden answered 13/2, 2014 at 22:15 Comment(2)
so for the last one if I used aBond.__del__() would it work?Ternopol
@Dannnno It would run your code, i.e. call self.end.removeBond(self) and self.start.removeBond(self). It would not free any objects or anything like that.Darden

© 2022 - 2024 — McMap. All rights reserved.