16

What are use cases in python 3 of writing a custom __del__ method or relying on one from stdlib1? That is, in what scenario is it reasonably safe, and can do something that's hard to do without it?

For many good reasons (1 2 3 4 5 6), the usual recommendation is to avoid __del__ and instead use context managers or perform the cleanup manually:

  1. __del__ is not guaranteed to be called if objects are alive on intrepreter exit2.
  2. At the point one expects the object can be destroyed, the ref count may actually be non-zero (e.g., a reference may survive through a traceback frame held onto by a calling function). This makes the destruction time far more uncertain than the mere unpredictability of gc implies.
  3. Garbage collector cannot get rid of cycles if they include more than 1 object with __del__
  4. The code inside __del__ must be written super carefully:
    • object attributes set in __init__ may not be present since __init__ might have raised an exception;
    • exceptions are ignored (only printed to stderr);
    • globals may no longer be available.

Update:

PEP 442 has made significant improvements in the behavior of __del__. It seems though that my points 1-4 are still valid?


Update 2:

Some of the top python libraries embrace the use of __del__ in the post-PEP 442 python (i.e., python 3.4+). I guess my point 3 is no longer valid after PEP 442, and the other points are accepted as unavoidable complexity of object finalization.


1I expanded the question from just writing a custom __del__ method to include relying on __del__ from stdlib.

2It seems that __del__ is always called on interpreter exit in the more recent versions of Cpython (does anyone have a counter-example?). However, it doesn't matter for the purpose of __del__'s usablity: the docs explicitly provide no guarantee about this behavior, so one cannot rely on it (it may change in future versions, and it may be different in non-CPython interpreters).

max
  • 49,282
  • 56
  • 208
  • 355
  • 1
    Why do you exclude the standard library in this question? –  Apr 27 '17 at 19:57
  • 1
    Because I (over)used `__del__` in my code, and I am planning to completely abandon it. Before I do, I want to make sure I'm not missing situations where I shouldn't avoid it. Since I (and most people on SO) don't contribute to stdlib, I wanted to keep the question simpler by excluding it. – max Apr 27 '17 at 19:59
  • @ali_m I was thinking about it too. So let's say your objects' lifetime is too open-ended to enclose in a context manager, so you end up relying on `__del__`. You must care about cleanup enough to bother writing `__del__`. And yet you don't mind if the cleanup occsionally never happens or happens much later than expected (since that's the semantics of `__del__`). What kind of cleanup would be a good fit? Maybe freeing memory, but that's already done by gc. Maybe helping gc to free memory where it struggles? – max Apr 27 '17 at 22:39
  • @max In some cases you only require the `__del__` call if you're close to running out of memory and most python implementations collect objects when memory is getting "sparse". So you don't care about "immediate cleanup" but only about "eventual cleanup". – MSeifert Apr 27 '17 at 22:44
  • @max `gc` can't free memory that was allocated outside of Python (e.g. by a call to `malloc` from some shared library). – ali_m Apr 27 '17 at 22:45
  • @max I'm not actually opposed that you changed the accepted answer (I like the new accepted answer - given that I actually encouraged the answer in the now deleted comments!) but this is a very interesting topic and you might want to consider leaving it unaccepted for a few more days because ["a question with an accepted answer isn't as likely to receive further attention as one without an accepted answer."](https://meta.stackexchange.com/a/5235/317868). I would be very interested if there are other interesting use-cases out there In the end it's your decision I just hoped for more answers. :) – MSeifert Apr 27 '17 at 23:48
  • @MSeifert sure! I am learning a lot from this discussion – max Apr 28 '17 at 00:05

3 Answers3

10

Context managers (and try/finally blocks) are somewhat more restrictive than __del__. In general they require you to structure your code in such a way that the lifetime of the resource you need to free doesn't extend beyond a single function call at some level in the call stack, rather than, say, binding it to the lifetime of a class instance that could be destroyed at unpredictable times and places. It's usually a good thing to restrict the lifetime of resources to one scope, but there sometimes edge cases where this pattern is an awkward fit.

The only case where I've used __del__ (aside from for debugging, c.f. @MSeifert's answer) is for freeing memory allocated outside of Python by an external library. Because of the design of the library I was wrapping, it was difficult to avoid having a large number of objects that held pointers to heap-allocated memory. Using a __del__ method to free the pointers was the easiest way to do cleanup, since it would have been impractical to enclose the lifespan of each instance inside a context manager.

ali_m
  • 71,714
  • 23
  • 223
  • 298
  • What did your `__del__` do that the normal garbage collection couldn't? – max Apr 27 '17 at 22:44
  • @max It freed memory that had been allocated outside of Python (by a C function in the library I was wrapping), and was therefore invisible to `gc`. – ali_m Apr 27 '17 at 22:47
  • 1
    I'll accept your answer even though both are very helplful because I think freeing of `malloc`-allocated memory is a perfect production use case. From my question, point (1) is irrelevant since the process memory will be freed on exit; point (2) is less relevant because (as @MSeifert points out) as long as `gc` frees it before memory completely runs out, it's good enough; (3) and (4) are just points that implementation needs to be careful about. – max Apr 27 '17 at 22:50
  • 1
    Wouldn't weakref callbacks be suitable for this purpose, without the downsides of `__del__`? – Marius Gedminas May 02 '17 at 10:24
  • @MariusGedminas AFAIU, `weakref` callbacks (combined with `weakref.finalize`) are strictly more powerful than `__del__`. The trick is to find a situation where, despite their disadvantages, `__del__` is ok to use. This seems to be such a case. Or do you see a particular problem with `__del__` even in this scenario? – max May 03 '17 at 11:18
  • 1
    @MariusGedminas I asked [a question](http://stackoverflow.com/questions/43758886/can-weakref-callbacks-replace-del) about this, since I think it's too much to add more side questions to this post. – max May 03 '17 at 11:36
  • @MariusGedminas Great point! I hadn't considered using `weakref` callbacks - I think they might very well be suitable for my use case. I wouldn't say that they lacked *all* the downsides of `__del__`, though (for example they can't propagate exceptions). – ali_m May 04 '17 at 21:07
8

One use-case is debugging. If you want to track the lifetime of a specific object it's convenient to write a temporary __del__ method. It can be used to do some logging or just to print something. I have used it a few times especially when I was interested in when and how instances are deleted. It's sometimes good to know when you create and discard a lot of temporary instances. But as I said I only ever used this to satisfy my curiosity or when debugging.

Another use-case is subclassing a class that defines a __del__ method. Sometimes you find a class that you want to subclass but the internals require you to actually override __del__ to control the order in which the instance is cleaned up. That's very rare too because you need to find a class with __del__, you nee to subclass it and you need to introduced some internals that actually require to call the superclass __del__ at exactly the right time. I actually did that once but I don't remember where and why it was important (maybe I didn't even know about alternatives then, so treat this as possible use-case).

When you wrap an external object (for example a object that isn't tracked by Python) that really, really needs to be deallocated even if someone "forgets" (I suspect a lot of people just omit them on purpose!) to use the context manager that you provided.


However all these cases are (or should be) very, very rare. Actually it's a bit like with metaclasses: They are fun and it's really cool to understand the concepts because you can probe the "fun parts" of python. But in practice:

If you wonder whether you need them [metaclasses], you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

Citation (probably) from Tim Peters (I haven't found the original reference).

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • The main problem that prevents destructors from being useful in production code seems to be the lack of predictability of when or whether they are called. The metaclass, being slightly complex but perfectly predictable, is a lot more useful! – max Apr 27 '17 at 22:17
  • @max Yes, I guess the comparison isn't that useful after all. I was purely thinking about use-cases: Both have use-cases, but they tend to be really sparse (and really complex). – MSeifert Apr 27 '17 at 22:25
  • BTW, another technique (which I just found out about) to help track the objects' lifetime, is to set `PYTHONVERBOSE=2`. – max Apr 27 '17 at 22:56
  • @max Isn't that just about modules? I never actually used it yet, I'll definetly give it a try :) But in my debugging scenarios I was only interested in one or two classes so there's no need to track all. :) – MSeifert Apr 27 '17 at 22:58
  • Ah yes, you're right, it only includes module cleanup info. – max Apr 27 '17 at 23:03
1

One case where i always use __del__, is for closing a aiohttp.ClientSession object.

When you don't, aiohttp will print warnings about the unclosed client session.

Willem Hengeveld
  • 2,758
  • 23
  • 19
  • 1
    But wouldn't that be unreliable, since based on the python documentation, your `__del__` may never be called? – max May 01 '17 at 14:39