Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Alongside Object Pascal back in the old MS-DOS and Apple days, Ada 95, Swift, Python, and a couple of others that I won't now bother to search for on my papers archive.


Here’s the crucial difference between Python destructors and C++ destructors: C++ destructors run as soon as the object goes out of scope, deterministically. Python destructors run down the road, during the next GC cycle.

As an example of why this matters, I’ve got a class called TimeCapture in a project. If I want to instrument how long a block takes (full function, inside of an if, etc) I can add `TimeCapture tc(“my-metric-name”);` to the block and know that the destructor on that object will be called precisely at the end of that block (capturing the end time of the block)

I don’t know the rules for Swift or Ada or OP, but I’m guessing they’ll vary.


> Here’s the crucial difference between Python destructors and C++ destructors: C++ destructors run as soon as the object goes out of scope, deterministically. Python destructors run down the road, during the next GC cycle.

Not true. They run when the object is deallocated. In CPython if the object is not involved in or referenced from a cycle, that’s as soon as it goes out of scope: the ref count goes to 0 and the object gets deallocated.

This is trivial to check: create a structure with a noisy __del__ and create an instance from a function, call the function, del will run. The GC has no reason to run at this point, there’s no allocation requiring any sort of reclamation, but if you don’t trust that you can just `gc.disable()` and observe… the exact same behaviour.


> Not true. They run when the object is deallocated. In CPython if the object is not involved in or referenced from a cycle, that’s as soon as it goes out of scope: the ref count goes to 0 and the object gets deallocated.

That's a CPython implementation detail, not a guarantee that you can rely on.

For example, both PyPy[0] and Jython use tracing GCs where this is not true.

[0]: https://doc.pypy.org/en/latest/cpython_differences.html


> That's a CPython implementation detail

Which would be why I specifically wrote « in CPython ».

And it still makes the original assertion completely incorrect. Python destructors do not run « down the road, during the next GC cycle ». They may, in certain cases or implementations.


Once again,

> implementation detail

There is no guarantee that even CPython will keep the current behaviour. Ultimately, the promise you're given is that __del__ might be invoked by the system some point between "the object has no live references" and "never".

The original assertion gives a much closer intuition than your description of the current behaviour.


Not all the world is CPython though. There are several other implemntions and the python docs make it quite clear that the cpython developers do not consider the a destructor running when an object goes out of scope anything more than an implementation detail, and they reserve the right to change it without warning should they discover a reason to. (They haven't changed it in 20 or 30 years, but that doesn't mean they won't tomorrow)


> Not all the world is CPython though

The original commenter made a specific assertion. I demonstrated that their assertion is not true.


But you didn't demonstrate anything because your assertion is false. In C++ destructor are guaranteed deterministic as to when they run. In Cpython they are not guaranteed, by chance it happens that way, but it should be considered chance.

It seems to me that there must be some other language that would have the c++ guarantee, but I don't know of it. (it isn't hard to implement, and c++ is well known enough that other languages designers can consider the pros and cons and surely someone else has decided it is worth it)


> It seems to me that there must be some other language that would have the c++ guarantee, but I don't know of it.

Rust's Drop[0] has basically the same guarantees as C++: the destructor will run right before the memory is freed (although it is possible in both cases to also leak both). Combine with Pin[1] if you need the exact memory location to be stable as well.

> it isn't hard to implement

Sadly, it's basically incompatible with tracing garbage collectors, since they make it completely unpredictable when the destructor will run.

So if you ever want to allow yourself to switch to tracing GC then you'll either have to disallow it completely, or bury it in a hidden corner of the docs with a load of caveats (like Python or Java). And in the latter case you'll still have people like the GP stumble upon it, say "oh, this looks neat", and set up a really painful trap for their future selves.

[0]: https://doc.rust-lang.org/stable/std/ops/trait.Drop.html

[1]: https://doc.rust-lang.org/stable/std/pin/index.html#drop-gua...


To implement scope guard you can use with statement and implement __enter__, __exit__ methods. It is not real RAII as object is not destroyed on exit but it is still quite useful.

http://effbot.org/zone/python-with-statement.htm


How do multiple destructors get sequenced? Last in, first out? Thanks




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: