> On Nov 19, 2019, at 08:04, Random832 <random...@fastmail.com> 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 -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/376S4F4WSNSR2XUQOLQEWEMDI2R3MD2C/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to