[Python-Dev] With statement

Alex Martelli aleax@aleax.it
Tue, 4 Feb 2003 16:05:56 +0100


On Tuesday 04 February 2003 03:00 pm, Moore, Paul wrote:
   ...
> The with version also avoids the common (at least with me) mistake of
>
>     try:
>         f = open("file")
> 	# Use f
>     finally:
>         f.close()

Yes, it IS a common mistake, and well worth avoiding, true.

> > With name binding,
> >
> >     with f = autoclosedfile("whatever"):
> >         # Use f
> >
> > we're down to 1, and in this case the advantage strikes me
> > as substantial.
>
> But this uses a different approach entirely. Here, autoclosedfile is a
> self-managing file subclass, whereas the autoclose() example used an
> independent manager class. I know the point is subtle to the extent of

Then code it as:
    with f = autoclosedfile(open("whatever")):

and have your "independent manager class", no problem.  I'd rather
have the imc in question "wrap" the file anyway (delegating to the
file object any getattr it doesn't know about), but it's no big deal if
you want to do it otherwise, with an explicit f.thefile or whatever.


> > It would also be more natural for people who are used to coding "resource
> > acquisition is initialization" in C++, since in that case, too, a name is
> > always required (whether you use it or not in the following block):
> >
> > {
> >     autoclosedfile f("whatever");
> >     // Use f
> > }
>
> But there are other subtle differences - many resources can be acquired in
> the one block, often not at the start of the block either. Whatever way you
> play it, "with" is a weak substitute.

C++ doesn't require syntactically explicit nesting of such "blocks" (nor
braces for them) but semantically the nesting IS there anyway.  E.g.:

{
    oneresource a;
    fooble(a);
    anotherone b;
    grepze(a, b);
}

if fooble(a) throws (==raises), a's destructor runs but not b's (nor b's
constructor).  When teaching C++ I always explained this kind of thing
as fully equivalent to:

{
    oneresource a;
    fooble(a);
    {
        anotherone b;
        grepze(a, b);
    }
}

and I saw MANY eyes light up in "ah, FINALLY I understand it!" ways
when I showed this equivalence.

So, I disagree that "with" (WITH binding ability) is a weak substitute --
it's "explicit is better than implicit" in term of "nesting", but that's not
necessarily a weakness.  WITHOUT binding abilty, then yes, "with"
is arguably (I'm not sure...) too weak to be worth having.


> What really kills it for me is the fact that personally, I like the
> no-assignment cases for locks:

But that doesn't work in C++ -- there HAS to be a name binding
for a RAI -- and I think that, if the name binding is either forbidden
or mandatory (i.e. too hard to make optional), C++ has it right
here -- mandatory is better than forbidden.


>     class Lock:
>         # Whatever you need here
> 	def __enter__(self):
> 	    self.acquire()
> 	def __exit__(self):
> 	    self.release()
>     ...
>
>     Lock GIL

meaning
    GIL = Lock()
I guess?  Too much C++...;-)

>     with GIL:
>         # Do something
>
>     ...
>
>     with GIL:
> 	# Do something else
>
> That to me looks like the canonical use for locking. Unfortunately, people

It is, but the price of writing "with GIL=GIL:" or whatever is pretty minor,
and quite comparable to the "canonical use for repetition" of having to
bind a name to the for-loop's iteration variable.


Alex