I prefer working in python to C++, I just wish I had a lot more control over things like this. And no, __del__ isn't a good solution as that opens a whole can of worms (for one, it's not guaranteed to ever be called).
a(b(c()))
because just returning an object, then dropping the reference at the end of 'a' would do just that. You could also write your own context manager to wrap up a parameter. class Shadow(object):
def __init__(self):
self.value = None
def __enter__(self):
return self
def set(self, v):
self.value = v
def __exit__(self, *args):
if self.value is not None:
self.value.cleanup()
with Newdb() as db, Shadow() as d:
a(b(c(db, d))) with a(b(c)) as f:
but then you risk any of those functions throwing an exception and screwing up the whole thing. You can move the resource allocation from __init__ to __enter__, but now you split your initialization code over 2 constructors. You can just hope and pray that a and b don't throw an exception, but someone is gonna accidentally break that assumption. And even if they don't, you've now spent more time worrying about that then if you just did manual memory allocation.Or, in C++ you write
class Foo {
public:
Foo() {/*get handle*/}
~Foo() {/*release resource*/}
};
and you never have to worry again.After giving it some thought, I've come up with a better example which more closely maps to C++ RAII constructs, which shows that you don't need to split anything. Consider:
class Shadow(object):
def __init__(self):
# Initialization code
@classmethod
def __enter__(cls, *args, **kwargs):
return cls(*args, **kwargs)
def __exit__(self, *args, **kwargs):
# De-initialize
`__enter__` becomes three lines of boilerplate, which you could abstract out into an inheritable class, should you so desire.As for "In C++ you write", it's the same as saying "in Python you write __enter__ and __exit__", but instead of instantiating via `std::unique_ptr` you use `with`.
with alloc_resource() as resource:
resource.use()You complain about having to put initialization into 2 constructors, but there's the flip-side with things like mutexes. In c++, you have to have 2 classes, one for the lock and one for the guard. Whereas with a context-manager, you have one class and the locking code is in the __enter__ and __exit__.
with self.lock:
self.do_stuff()
In that case, I would say that the context-manager is nicer.I haven't done much with mutexes, but doesn't having it split over 2 classes introduce a race condition? Seems like a weird way to implement it to me.
{
Type obj;
// do stuff
}
// obj destructor just ran
C# and other languages have similar paradigms, but they require more attention to detail than a destructor does.