I have searched and I\'m unable to come up with any good reason to use python\'s __enter__
/__exit__
rather than __init__
(or __new_
There are several differences you appear to have missed:
Context manager get a chance to provide a new object just for the block you are executing. Some context managers just return self
there (like file objects do), but, as an example, database connection objects could return a cursor object tied to the current transaction.
Context managers are not just notified of the context ending, but also if the exit was caused by an exception. It can then decide on handling that event or otherwise react differently during exit. Using a database connection as an example again, based on there being an exception you could either commit or abort the transaction.
__del__
is only called when all references to an object are removed. This means you can't rely on it being called if you need to have multiple references to it that you may or may not control the lifetime of. A context manager exit is precisely defined however.
Context managers can be reused, and they can keep state. The database connection again; you create it once, then use it as a context manager again and again, and it'll keep that connection open. There is no need to create a new object each time for this.
This is important for thread locks, for example; you have to keep state so that only one thread can hold the lock at a time. You do this by creating one lock object, then use with lock:
so different threads executing that section each can be made to wait before entering that context.
The __enter__
and __exit__
methods form the context manager protocol, and you should only use these if you actually want to manage a context. The goal of context managers is to simplify common try...finally
and try...except
patterns, not to manage the lifetime of a single instance. See PEP 343 – The "with" Statement:
This PEP adds a new statement "with" to the Python language to make it possible to factor out standard uses of try/finally statements.
By using these instead of
__init__
/__del__
I appear to be creating an implicit contract with callers that they must usewith
, yet there's no way to enforce such a contract
You have a contract either way. If users use your object without realizing it requires cleanup after use, they'll screw things up no matter how you implement the cleanup. They might keep a reference to your object forever, for example, preventing __del__
from running.
If you have an object that requires special cleanup, you need to make this requirement explicit. You need to give users with
functionality and an explicit close
or similar method, to let users control when the cleanup occurs. You can't hide the cleanup requirement inside a __del__
method. You might want to implement __del__
too, as a safety measure, but you can't use __del__
in place of with
or an explicit close
.
With that said, Python makes no promises that __del__
will run, ever. The standard implementation will run __del__
when an object's refcount drops to 0, but that might not happen if a reference survives to the end of the script, or if the object is in a reference cycle. Other implementations don't use refcounting, making __del__
even less predictable.
del x
doesn’t directly call x.__del__()
You have no control over when .__del__
is called, or in fact whether it gets called at all.
Therefore, using __init__
/__del__
for context management is not reliable.