[Python-ideas] With clauses for generator expressions

Andrew Barnert abarnert at yahoo.com
Fri Nov 16 10:32:22 CET 2012


I'm pretty sure both my original message and the blog post linked from there 
explained why this is not particularly useful for list comprehensions. (If 
you're guaranteed to exhaust the iteration in the current block—which you 
obviously always are for comprehensions—just make the with a statement with its 
own block.)

The only reason I suggested it for comprehensions as well as generator 
expressions is that someone convinced me that it would be slightly easier to 
implement, and to teach to users, than if it were only available for generator 
expressions.


From: Mathias Panzenböck <grosser.meister.morti at gmx.net>
Sent: Thu, November 15, 2012 8:39:34 PM
> 
> Oh yes, you're right. Didn't think of this. Maybe I should go to bed/not write  
>comments here at 5:38am.
> 
> On 11/16/2012 05:27 AM, Guido van Rossum  wrote:
> > On Thu, Nov 15, 2012 at 8:12 PM, Mathias Panzenböck
> >  <grosser.meister.morti at gmx.net>  wrote:
> >> I think this syntax would still make sense for list  comprehensions:
> >>
> >> upperlines = [lines.upper() for line in  file with open('foo', 'r') as 
file]
> >
> > -1000. There is no  discernible advantage over
> >
> > with open(...) as file:
> >     upperlines = [lines.upper() for line in file]
> >
> > Also you've  got the order backwards -- when there's a sequence of
> > 'for' and 'if'  clauses in a comprehension, they are to be read from
> > left to right, but  here you're tacking something onto the end that's
> > supposed to go  first.
> >
> > Please don't destroy my beautiful  language.
> >
> > --Guido
> >
> >> On 11/15/2012 10:29 AM,  Masklinn wrote:
> >>>
> >>>
> >>> On 2012-11-15,  at 04:44 , Andrew Barnert wrote:
> >>>
> >>>> First, I  realize that people regularly propose with expressions. This  
is
> >>>> not the
> >>>> same  thing.
> >>>>
> >>>> The problem with the with  statement is not that it can't be postfixed
> >>>> perl-style, or  used in expressions. The problem is that it can't be used
> >>>>  with
> >>>> generator  expressions.
> >>>>
> >>>> Here's the  suggestion:
> >>>>
> >>>>       upperlines = (lines.upper() for line in file with open('foo', 'r')  
>as
> >>>> file)
> >>>>
> >>>> This would  be equivalent to:
> >>>>
> >>>>       def foo():
> >>>>          with  open('foo', 'r') as file:
> >>>>               for line in file:
> >>>>                   yield line.upper()
> >>>>       upperlines = foo()
> >>>>
> >>>> The  motivation is that there is no way to write this properly using  a
> >>>> with
> >>>> statement and a generator  expression—in fact, the only way to get this
> >>>> right  is
> >>>> with the generator function  above.
> >>>
> >>>
> >>> Actually, it's extremely  debatable that the generator function is
> >>> correct: if the  generator is not fully consumed (terminating iteration
> >>> on the  file) I'm pretty sure the file will *not* get closed save by the
> >>>  GC doing a pass on all dead objects maybe. This means this function  is
> >>> *not safe* as a lazy source to an arbitrary client, as that  client may
> >>> very well use itertools.slice or itertools.takewhile  and only partially
> >>> consume the  generator.
> >>>
> >>> Here's an  example:
> >>>
> >>> --
> >>> import  itertools
> >>>
> >>> class  Manager(object):
> >>>       def  __enter__(self):
> >>>           return  self
> >>>
> >>>       def __exit__(self,  *args):
> >>>            print("Exited")
> >>>
> >>>       def  __iter__(self):
> >>>           for i in  range(5):
> >>>               yield  i
> >>>
> >>> def foo():
> >>>        with Manager() as ms:
> >>>           for m  in ms:
> >>>               yield  m
> >>>
> >>> def bar():
> >>>        print("1")
> >>>       f = foo()
> >>>        print("2")
> >>>       # Only consume  part of the iterable
> >>>        list(itertools.islice(f, None, 2))
> >>>        print("3")
> >>>
> >>> bar()
> >>>  print("4")
> >>> --
> >>>
> >>> CPython output,  I'm impressed that the refcounting GC actually bothers
> >>> unwinding  the stack and running the __exit__ handler *once bar has
> >>>  finished executing*:
> >>>
> >>>> python3 withgen.py
> >>>
> >>>  1
> >>> 2
> >>> 3
> >>> Exited
> >>>  4
> >>>
> >>> But here's the (just as correct, as far as I  can tell) output from pypy:
> >>>
> >>>> pypy-c  withgen.py
> >>>
> >>> 1
> >>> 2
> >>>  3
> >>> 4
> >>>
> >>> If the program was long  running, it is possible that pypy would run
> >>> __exit__ when the  containing generator is released (though by no means
> >>> certain, I  don't know if this is specified at all).
> >>>
> >>> This is  in fact one of the huge issues with faking dynamic scopes via
> >>>  threadlocals and context managers (as e.g. Flask might do, I'm not  sure
> >>> what actual strategy it uses), they interact rather weirdly  with
> >>> generators (it's also why I think Python should support  actually
> >>> dynamically scoped variables, it would also fix the  thread-broken
> >>> behavior of e.g.  warnings.catch_warnings)
> 
> 
> _______________________________________________
> Python-ideas  mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
> 



More information about the Python-ideas mailing list