> On Nov 19, 2019, at 08:04, Random832 <[email protected]> wrote:
> On Tue, Nov 19, 2019, at 07:03, Paul Moore wrote:
>> That sounds reasonable, with one proviso. I would *strongly* object to
>> calling context managers that conform to the new expectations "well
>> behaved", and by contrast implying that those that don't are somehow
>> "misbehaving". File objects have been considered as perfectly
>> acceptable context managers since the first introduction of context
>> managers (so have locks, and zipfile objects, which might also fall
>> foul of the new requirements). Suddenly deeming them as "misbehaving"
>> is unreasonable.
>
> The problem is that if this model is perfectly okay, then *there's no reason
> for __enter__ to exist at all*. Why doesn't *every* context manager just do
> *everything* in __init__? I think it's clear that something was lost between
> the design and the implementation.
Forget about context managers for a second. A class can bind attributes in
__new__ and return a fully initialized object. If that’s perfectly ok, why
doesn’t every class do everything in __new__, in which case there’s no reason
for __init__ to exist at all? But in fact, it’s usually a good idea to bind
your mutable attributes and attributes that you expect subclasses to override
in __init__. This signals your intentions better, and makes it easier to use
your class with a range of optional utilities. But it’s not mandatory, and
sometimes there are good reasons to violate it. And yet, despite the split
being entirely up to the class writers and there being no hard rules about it,
it’s still useful.
So, why can’t much the same be true for context managers? It’s usually a good
idea to do resource acquisition in __enter__, and also to have __init__ never
raise. This signals your intentions better, and makes it easier to use your cm
with a range of optional utilities, but it’s not mandatory, and sometimes there
are good reasons not to.
A language could certainly live without the distinction, but it could also live
without two-phase initialization. (C++ merges all of __new__, __init__, and
__enter__ into one constructor, and __del__ and __exit__ into one destructor,
and “resource acquisition is initialization” would work perfectly if not for
half the stdlib and 80% of the third-party ecosystem being inherited without
wrappers from C, and therefore not exception safe…). But that doesn’t mean a
language can’t benefit from the distinction.
For example, notice that Python doesn’t have C++‘s complicated member
destructor rules or ObjC’s different kinds of attributes to manage ARC, and yet
we can still get away with having resources with dynamic lifetimes (tied to an
owning object rather than a lexical scope). That works because resources can
easily be used manually, rather than every resource being a context manager and
only usable that way; otherwise we’d need language or library support for
managing your attribute context. It isn’t perfect (you can’t screw up RAII in
C++ if you only use RAII objects; you can easily screw up cleanup in Python
even with objects that have cm support), but it mostly works. Arguably we’re
getting half the benefit of an RAII system with only a quarter of the costs.
(And part of the cost we’re skipping may be that it’s nearly impossible to add
non-refcounting GC to an implementation of C++ or ObjC, but pretty easy for
Python.)
I can imagine other designs that might have the same benefit and still not
require __enter__. For example, make ExitStack syntactic and then eliminate the
cm machinery:
scope:
f1 = open(fn1)
defer f1.close()
f2 = open(fn2)
defer f2.close()
More verbose, but simpler, and it means you don’t need to write anything to
make an object manageable; the name “close” is just a convention rather than
machinery. (For cleanup that’s not a single expression, you’d have to factor it
out into a function or method—but that’s at worst equivalent to writing the
__exit__ method today, and now you’d only need that for complicated resource
managers rather than for all of them.)
Or just go back to Python 2.2 and mandate deterministic destruction (and if
that means Jython can’t be simple and efficient, so be it) and then build from
there. Now all you need is a way to create scopes without manually defining and
calling nested functions and you’re done.
But if we’re really going to rethink resource management from scratch, I don’t
think we’re talking about Python anymore anyway.
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/376S4F4WSNSR2XUQOLQEWEMDI2R3MD2C/
Code of Conduct: http://python.org/psf/codeofconduct/