Is this PEP-able? "with" statement inside genexps / list comprehensions

Hello,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
This
g = (f.read() for fn in filenames with open(fn) as f)
would be equivalent to the following use of a generator function:
def __gen(): for fn in filenames: with open(fn) as f: yield f.read() g = __gen()
This
list = [f.read() for fn in filenames with open(fn) as f]
would be equivalent to the following:
list = [] for fn in filenames: with open(fn) as f: list.append(f.read())
-- Rudy

Rudy,
I think your proposal may be very specific to iterable context managers; in which case, make a method that makes that assumption:
def iter_with(obj): with obj as context: yield from context
and use it
g = ( f.read() for fn in filenames for f in iter_with(open(fn)) )
On 2018-07-30 15:15, Rudy Matela wrote:
Hello,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
This
g = (f.read() for fn in filenames with open(fn) as f)
would be equivalent to the following use of a generator function:
def __gen(): for fn in filenames: with open(fn) as f: yield f.read() g = __gen()
This
list = [f.read() for fn in filenames with open(fn) as f]
would be equivalent to the following:
list = [] for fn in filenames: with open(fn) as f: list.append(f.read())
-- Rudy _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Monday, July 30, 2018 at 3:55:25 PM UTC-4, Kyle Lahnakoski wrote:
Rudy,
I think your proposal may be very specific to iterable context managers;
I don't think his proposal is specific to iterable context managers. You can have a with clause that is used in a following for clause.
in which case, make a method that makes that assumption:
def iter_with(obj): with obj as context: yield from context
and use it
g = ( f.read() for fn in filenames for f in iter_with(open(fn)) )
On 2018-07-30 15:15, Rudy Matela wrote:
Hello,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
This
g = (f.read() for fn in filenames with open(fn) as f)
would be equivalent to the following use of a generator function:
def __gen(): for fn in filenames: with open(fn) as f: yield f.read() g = __gen()
This
list = [f.read() for fn in filenames with open(fn) as f]
would be equivalent to the following:
list = [] for fn in filenames: with open(fn) as f: list.append(f.read())
-- Rudy _______________________________________________ Python-ideas mailing list Python...@python.org javascript: https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python...@python.org javascript: https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On 30 July 2018 at 20:15, Rudy Matela rudy@matela.com.br wrote:
Hello,
Hi Rudy,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
This
g = (f.read() for fn in filenames with open(fn) as f)
would be equivalent to the following use of a generator function:
def __gen(): for fn in filenames: with open(fn) as f: yield f.read() g = __gen()
Yielding from a with block should be discouraged rather than given special syntax. There is essentially a contradiction between the meaning/purpose of yield (suspend indefinitely) and with (definitely call __exit__).
If I partially iterate over g as in
for line in g: break
then at this point g is suspended and f.__exit__ has not been called, so the file is not closed. I may choose to iterate over g later or not, so it has to remain in suspension just in case. In practice if you do this in CPython then f.__exit__ will *probably* be invoked indirectly by g.__del__ if/when the gc collects g. This defeats the main point of using with-open though which is to avoid depending on the gc for closing files.
-- Oscar

31.07.18 00:26, Oscar Benjamin пише:
On 30 July 2018 at 20:15, Rudy Matela rudy@matela.com.br wrote: Yielding from a with block should be discouraged rather than given special syntax. There is essentially a contradiction between the meaning/purpose of yield (suspend indefinitely) and with (definitely call __exit__).
If I partially iterate over g as in
for line in g: break
then at this point g is suspended and f.__exit__ has not been called, so the file is not closed. I may choose to iterate over g later or not, so it has to remain in suspension just in case. In practice if you do this in CPython then f.__exit__ will *probably* be invoked indirectly by g.__del__ if/when the gc collects g. This defeats the main point of using with-open though which is to avoid depending on the gc for closing files.
Concur with Oscar.

On 30/07/18 20:15, Rudy Matela wrote:
Hello,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
I was worried until I twigged that you meant adding a clause rather than a statement per se. How does this interact with the other clauses in a comprehension? I'm thinking of monstrosities like:
g = (f.read() for fn in filenames if fn.endswith(".txt") with open(fn) as f for filenames in get_directory_listing(dir) if .....)

On 30/07/18 21:15, Rudy Matela wrote:
Hello,
Do you think it would be nice to allow with statements inside genexps or list comprehensions? The functions __enter__ and __exit__ would be automatically called as iterables are traversed. I am thinking of drafting a PEP about this. Examples:
This
g = (f.read() for fn in filenames with open(fn) as f)
would be equivalent to the following use of a generator function:
def __gen(): for fn in filenames: with open(fn) as f: yield f.read() g = __gen()
To sail around Oscar's concern, this should rather be
def __gen(): for fn in filenames: with open(fn) as f: _v = f.read() yield _v
But in this case I think it'd be clearer to make it an expression rather than a generator expression term:
g = (f.read() with open(fn) as f for fn in filenames)
where
_ = f.read() with open(fn) as f
is equivalent to
with open(fn) as f: _ = f.read()
Currently possibly (if silly) alternative:
from functools import wraps
class with_one_use: def __init__(self, context): self.__context = context
def __getattr__(self, name): exc1 = False obj = self.__context.__enter__() try: temp = getattr(obj, name) except: exc1 = True if not self.__context.__exit__(*sys.exc_info()): raise else: if callable(temp): @wraps(temp) def f(*args, **kwargs): exc2 = False try: return temp(*args, **kwargs) except: exc2 = True if not self.__context.__exit__(*sys.exc_info()): raise finally: if not exc2: self.__context.__exit__(None, None, None) exc1 = True return f else: return temp finally: if not exc1: self.__context.__exit__(None, None, None)
g = (with_one_use(open(fn)).read() for fn in filenames)
-- Thomas
This
list = [f.read() for fn in filenames with open(fn) as f]
would be equivalent to the following:
list = [] for fn in filenames: with open(fn) as f: list.append(f.read())
-- Rudy _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
participants (7)
-
Kyle Lahnakoski
-
Neil Girdhar
-
Oscar Benjamin
-
Rhodri James
-
Rudy Matela
-
Serhiy Storchaka
-
Thomas Jollans