Re: [Python-ideas] Is this PEP-able? "with" statement inside genexps / list comprehensions

2018-08-08 Thread Thomas Jollans
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/
> 

___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Yury Selivanov
On Wed, Aug 8, 2018 at 12:28 PM Paul Moore  wrote:
>
> On Wed, 8 Aug 2018 at 16:58, Oscar Benjamin  
> wrote:
> > Thinking about this some more: closing() can ensure finalisation but
> > it is still generally bad to yield from a with block. Some context
> > managers are designed to temporarily alter global state between
> > __enter__ and __exit__ - this can be very confusing if you use them
> > around a yield block.
>
> Surely that's *precisely* what PEP 567 (Context Variables) was
> designed to address?

To be even more precise, PEP 550 was designed to solve state handling
problem for both "await" (coroutines) and "yield" (generators). It was
rejected in favour of a more simplistic PEP 567 that solves it only
for coroutines.

PEP 568 was later drafted by Nathaniel. It applies PEP 550's ideas to
567 API/design to allow generators to have execution context too in
Python 3.8 or 3.9. The fate of PEP 568 is still undecided.

Yury
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Paul Moore
On Wed, 8 Aug 2018 at 16:58, Oscar Benjamin  wrote:
> Thinking about this some more: closing() can ensure finalisation but
> it is still generally bad to yield from a with block. Some context
> managers are designed to temporarily alter global state between
> __enter__ and __exit__ - this can be very confusing if you use them
> around a yield block.

Surely that's *precisely* what PEP 567 (Context Variables) was
designed to address?

Paul
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Rhodri James

On 08/08/18 16:55, Oscar Benjamin wrote:

Thinking about this some more: closing() can ensure finalisation but
it is still generally bad to yield from a with block. Some context
managers are designed to temporarily alter global state between
__enter__ and __exit__ - this can be very confusing if you use them
around a yield block.


Yes, but those sorts of context managers need to be used with care in 
all sorts of circumstances.  Fiddling with global state is always fraught.


--
Rhodri James *-* Kynesim Ltd
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Peter Otten
Ronald Oussoren via Python-ideas wrote:

> It is also possible to fix the particular issue by using another with
> statement, that is use:
> 
> with contextlib.closing(read_multiple(…)) as chunks:
>for contents in chunks:
>…
> 
> Automatically closing the generator at the end of the for loop would be
> nice, but getting the semantics right without breaking existing valid code
> is not trivial.

How about providing a decorator that turns the generator into a context 
manager

def close_gen(f):
@contextmanager
@wraps(f)
def g(*args, **kw):
with closing(f(*args, **kw)) as h:
yield h
return g

@close_gen
def read_multiple(...):
...

?

A more radical approach would be to add __enter__ and __exit__ methods so 
that for every generator function f

with f() as g:
   pass

would call g.close()

___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Oscar Benjamin
On 8 August 2018 at 15:22, Ronald Oussoren via Python-ideas
 wrote:
>>
>> On 08/08/18 07:14, Ken Hilton wrote:
>>> Now, let's take a look at the following scenario:
>>> def read_multiple(*filenames):
>>> for filename in filenames:
>>> with open(filename) as f:
>>> yield f.read()
>>> Can you spot the problem? The "with open(filename)" statement is supposed
>>> to ensure that the file object is disposed of properly. However, the "yield
>>> f.read()" statement suspends execution within the with block, so if this
>>> happened:
>>> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
>>> if contents == 'hello':
>>> break
>>> and the contents of "chunk2" were "hello" then the loop would exit, and
>>> "chunk2" would never be closed! Yielding inside a with block, therefore,
>>> doesn't make sense and can only lead to obscure bugs.
>
> It is also possible to fix the particular issue by using another with 
> statement, that is use:
>
> with contextlib.closing(read_multiple(…)) as chunks:
>for contents in chunks:
>…

That's a very good point Ronald. Having seen this a few times I can't
think of any cases that wouldn't be solved by this.

If a language change was wanted for this then perhaps it should be to
do like file objects and add the __exit__ (calls close()) method to
generator objects so that you can omit the closing and just do:

with read_multiple(…) as chunks:
for contents in chunks:
...

--
Oscar
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Chris Angelico
On Wed, Aug 8, 2018 at 10:32 PM, Oscar Benjamin
 wrote:
> Without the context manager you could write:
>
> def read_multiple(*filenames):
> for filename in filenames:
> f = open(filename)
> yield f.read()
> f.close()
>
> Which also only leaks one file descriptor. The point of the with
> statement is that this was considered unsatisfactory but when you
> yield from a with statement the advantage of with is lost.

What if you remove yield from the picture?

 def read_multiple(callback, *filenames):
 for filename in filenames:
 f = open(filename)
 callback(f.read())
 f.close()

This is a classic approach as done in many languages that lack
generators. The problem isn't that one file handle is being leaked
until the next iteration; it's that an exception could permanently
leak that file. Also of note is the "write-then-read" approach:

def writer(fn):
f = open(fn, "w")
f.write(...)
def reader(fn):
f = open(fn)
return f.read()
writer("foo"); reader("foo")

Without a guarantee that the file is closed *immediately* on losing
its last reference, this is risky, because the write handle might
still be open when the read handle is opened.

The context manager guarantees both of these situations, and that
doesn't change when a yield point is inserted, same as it doesn't
change when a callback is inserted. The generator mechanism inverts
the call stack a bit, but it isn't fundamentally different; at some
point in read_multiple, it waits for some other code to do its stuff,
then it continues.

> In fact if you're happy depending on garbage collection for cleanup
> then you can write this more conveniently:
>
> def read_multiple(*filenames):
> for filename in filenames:
> yield open(filename).read()

And that's the whole point: you cannot be happy with garbage
collection cleanup, because a non-refcounting Python implementation is
not guaranteed to clean these up promptly.

> In any case saying that you only leak one file descriptor misses the
> point. The with statement can do many different things and it's
> purpose is to guarantee *without depending on garbage collection* that
> the __exit__ method will be called. Yielding from the with statement
> circumvents that guarantee.

Technically, it guarantees that __exit__ will be called before the
next line of code after the 'with' block. It cannot actually guarantee
that it will be called:

with open(filename) as f:
while True: pass
print("Won't happen")
print("Won't happen either")

All that's guaranteed is that the second print call will not happen
*before* the file is closed.

> Actually the general way to solve this problem is to move all context
> managers out of iterators/generators. Instead of a helper generator
> what you really want in this situation is a helper context manager. I
> think the reason this isn't always used is just because it's a bit
> harder to write a context manager than a generator e.g.:
>
> from contextlib import contextmanager
>
> @contextmanager
> def open_cat_split(*filenames):
> f = None
> def get_words():
> nonlocal f
> for filename in filenames:
> with open(filename) as f:
> for line in f:
> yield from line.split()
> else:
> f = None
> try:
> yield get_words()
> finally:
> if f is not None:
> f.close()
>
> Then you can do:
>
> with open_cat_split('file1.txt', 'file2.txt') as words:
> for word in words:
> print(word)
>

So... you still have a yield (in this case a "yield from") inside your
'with' block, but you've wrapped it up in an extra layer, and done a
manual try/finally. How is this different? Looking only at the lower
half of the function, you have this:

try:
yield # <== the function stops here
finally:
# <== the function cleans up here

This is exactly the same as the context manager situation. The finally
block cannot be executed until the function resumes.

What you have accomplished here is a level of nesting, and you can
achieve that far more simply by just closing the context manager
itself. Going right back to the original "problem code":

def read_multiple(*filenames):
for filename in filenames:
with open(filename) as f:
yield f.read()

for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
if contents == 'hello':
break

We can keep the generator completely unchanged, and add a context
manager to the loop, since we're not going to pump it completely:

  with contextlib.closing(read_multiple('chunk1', 'chunk2', 'chunk3')) as stuff:
  for contents in stuff:
  if contents == 'hello':
  break

Now, when you exit the loop, the generator will be closed, which uses
normal exception handling to clean up everything inside that function.
The files are guaranteed to 

Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Steven D'Aprano
On Tue, Aug 07, 2018 at 11:31:35PM -0700, Elazar wrote:

> Instead of disallowing code, this is a case for allowing with expressions:

Only if you accept Ken's conclusion, that yielding inside a with 
statement is harmful (I don't). And if you do accept Ken's conclusion, 
then with-expressions don't fix the problem of yielding from inside a 
with statement.

(Just because with-expressions are permitted doesn't mean that all the 
thousands of with-statements will be instantly re-written to use them.)


-- 
Steve

___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Ronald Oussoren via Python-ideas


> On 8 Aug 2018, at 14:35, Rhodri James  wrote:
> 
> On 08/08/18 07:14, Ken Hilton wrote:
>> Now, let's take a look at the following scenario:
>> def read_multiple(*filenames):
>> for filename in filenames:
>> with open(filename) as f:
>> yield f.read()
>> Can you spot the problem? The "with open(filename)" statement is supposed
>> to ensure that the file object is disposed of properly. However, the "yield
>> f.read()" statement suspends execution within the with block, so if this
>> happened:
>> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
>> if contents == 'hello':
>> break
>> and the contents of "chunk2" were "hello" then the loop would exit, and
>> "chunk2" would never be closed! Yielding inside a with block, therefore,
>> doesn't make sense and can only lead to obscure bugs.
> 
> An incomplete analysis and therefore an incorrect conclusion.  Until the 
> garbage collector comes out to play, read_multiple() will keep the file open, 
> keep the tuple of filenames in memory and of course keep the environment of 
> the generator around.  That's all leakage caused by the _break_, and 
> following your logic the obvious solution would be to ban breaks in loops 
> that are reading from generators.  But that wouldn't be helpful, obviously.

It is also possible to fix the particular issue by using another with 
statement, that is use:

with contextlib.closing(read_multiple(…)) as chunks:
   for contents in chunks:
   …

Automatically closing the generator at the end of the for loop would be nice, 
but getting the semantics right without breaking existing valid code is not 
trivial.

Ronald
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Rhodri James

On 08/08/18 07:14, Ken Hilton wrote:

Now, let's take a look at the following scenario:

 def read_multiple(*filenames):
 for filename in filenames:
 with open(filename) as f:
 yield f.read()

Can you spot the problem? The "with open(filename)" statement is supposed
to ensure that the file object is disposed of properly. However, the "yield
f.read()" statement suspends execution within the with block, so if this
happened:

 for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
 if contents == 'hello':
 break

and the contents of "chunk2" were "hello" then the loop would exit, and
"chunk2" would never be closed! Yielding inside a with block, therefore,
doesn't make sense and can only lead to obscure bugs.


An incomplete analysis and therefore an incorrect conclusion.  Until the 
garbage collector comes out to play, read_multiple() will keep the file 
open, keep the tuple of filenames in memory and of course keep the 
environment of the generator around.  That's all leakage caused by the 
_break_, and following your logic the obvious solution would be to ban 
breaks in loops that are reading from generators.  But that wouldn't be 
helpful, obviously.


--
Rhodri James *-* Kynesim Ltd
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Oscar Benjamin
On 8 August 2018 at 07:32, Chris Angelico  wrote:
> On Wed, Aug 8, 2018 at 4:14 PM, Ken Hilton  wrote:
>>
>> Now, let's take a look at the following scenario:
>>
>> def read_multiple(*filenames):
>> for filename in filenames:
>> with open(filename) as f:
>> yield f.read()
>>
>> Can you spot the problem? The "with open(filename)" statement is supposed to
>> ensure that the file object is disposed of properly. However, the "yield
>> f.read()" statement suspends execution within the with block, so if this
>> happened:
>>
>> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
>> if contents == 'hello':
>> break
>>
>> and the contents of "chunk2" were "hello" then the loop would exit, and
>> "chunk2" would never be closed! Yielding inside a with block, therefore,
>> doesn't make sense and can only lead to obscure bugs.
>
> This is only a problem if you consider it to be one. The value of the
> 'with' statement is not destroyed; for example, you're capped at _one_
> open file (whereas without the context manager, it's entirely possible
> for file-open in a loop to leak a large number of handles).

Without the context manager you could write:

def read_multiple(*filenames):
for filename in filenames:
f = open(filename)
yield f.read()
f.close()

Which also only leaks one file descriptor. The point of the with
statement is that this was considered unsatisfactory but when you
yield from a with statement the advantage of with is lost.

In fact if you're happy depending on garbage collection for cleanup
then you can write this more conveniently:

def read_multiple(*filenames):
for filename in filenames:
yield open(filename).read()

Or even:

for text in (open(filename).read() for filename in filenames):
# stuff

In any case saying that you only leak one file descriptor misses the
point. The with statement can do many different things and it's
purpose is to guarantee *without depending on garbage collection* that
the __exit__ method will be called. Yielding from the with statement
circumvents that guarantee.

>> I believe all possible cases where one would yield inside a context manager
>> can be covered by saving anything required from the context manager and then
>> yielding the results outside. Therefore, I propose making a "yield" inside a
>> with block become a SyntaxError.
>
> What about this:
>
> def read_words(*filenames):
>  for filename in filenames:
>  with open(filename) as f:
>  for line in f:
>  yield from line.split()
>
> It'll read from a series of files and yield individual words (ignoring
> annoying little things like punctuation and hyphenation for the sake
> of simplicity). You are assuming that every yield-inside-with is a
> *single* yield.

Actually the general way to solve this problem is to move all context
managers out of iterators/generators. Instead of a helper generator
what you really want in this situation is a helper context manager. I
think the reason this isn't always used is just because it's a bit
harder to write a context manager than a generator e.g.:

from contextlib import contextmanager

@contextmanager
def open_cat_split(*filenames):
f = None
def get_words():
nonlocal f
for filename in filenames:
with open(filename) as f:
for line in f:
yield from line.split()
else:
f = None
try:
yield get_words()
finally:
if f is not None:
f.close()

Then you can do:

with open_cat_split('file1.txt', 'file2.txt') as words:
for word in words:
print(word)

I am not sure exactly what the best primitive would be to make this
sort of thing easier. I most often see examples of this when someone
wants to process multiple files concatenated. The stdlib fileinput
module has a context manager that *almost* works:
https://docs.python.org/3.7/library/fileinput.html#fileinput.input

The corner case that isn't handled correctly by fileinput.input is
that when the list of filenames is empty it uses sys.argv or stdin
which is unlikely to be what someone wants in most of these
situations. You could fix that with

from contextlib import contextmanager
import fileinput

def open_cat(*filenames):
if not filenames:
@contextmanager
def dummy():
yield ()
return dummy()
else:
return fileinput.input(filenames)

And with that you could do:

def map_split(strings):
for string in strings:
yield from string.split()

with open_cat('file1.txt', 'file2.txt') as lines:
for word in map_split(lines):
print(word)

Perhaps actually what is wanted is a more generic contextlib helper. I
guess ExitStack is intended for this sort of thing but it's a bit
unwieldy to use:

Re: [Python-ideas] File format for automatic and manual tests

2018-08-08 Thread Rhodri James

On 07/08/18 22:57, Victor Porton wrote:

This is an idea of a new PEP.

I propose to create a portable file format which will list command line 
options to run Python scripts with dual purpose:


You are going about this the wrong way.  If you want IDE-makers to use 
your putative file format, you are going to have to persuade them that 
it is useful.  Demanding that they do something because you like it is 
not persuasive.


(Personally I don't use IDEs, so you proposal is exactly no use to me. 
I don't think it's a suitable subject for a PEP.)


--
Rhodri James *-* Kynesim Ltd
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] File format for automatic and manual tests

2018-08-08 Thread Jonathan Fine
Hi Victor

Thank you for your contribution, regarding standards for Python tools.
Here is my two cents worth.

You wrote:
> We need a standard to make PyCharm and others to conform to it.

Things don't quite work that way, in the Python community. True, we
have a Benevolent Dictator For Life (BDFL), but that's more an
expression of respect for Guido, than a position of power. And anyway,
he's on well-deserved vacation.

For what you're suggesting, it's more that standards emerge through
community use, and then are adopted as a PEP Python standard. Wherever
possible, start first with an 'ad hoc' standard.

You say your proposal enhances PyCharm. So ask PyCharm and its users
set something up. As a first step, solve your own personal problem,
and put it up somewhere where others can pick it up.

Prove that the idea is good by establishing a group of users, starting
perhaps with yourself.

-- 
Jonathan
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] File format for automatic and manual tests

2018-08-08 Thread Victor Porton

On 08/08/18 12:25, Barry Scott wrote:

On Tuesday, 7 August 2018 22:57:51 BST Victor Porton wrote:

This is an idea of a new PEP.

I propose to create a portable file format which will list command line
options to run Python scripts with dual purpose:

At the moment I solve this problem with various solutions, depending on
requirements.

* use python unittest
* add a test target to a makefile.
* write a bash script to run the tests and diff output if required
* on windows do the same with CMD scripts
* use python to run python in a subprocess that run the tests.

Given all these ways to solve the problem what extra are you looking for?


As I notes, I want to keep it in sync with PyCharm debug targets.

We need a standard to make PyCharm and others to conform to it.
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Oscar Benjamin
On 8 August 2018 at 08:48, Nathaniel Smith  wrote:
> On Tue, Aug 7, 2018 at 11:14 PM, Ken Hilton  wrote:
>>
>> Now, let's take a look at the following scenario:
>>
>> def read_multiple(*filenames):
>> for filename in filenames:
>> with open(filename) as f:
>> yield f.read()
>>
>> Can you spot the problem? The "with open(filename)" statement is supposed to
>> ensure that the file object is disposed of properly. However, the "yield
>> f.read()" statement suspends execution within the with block, so if this
>> happened:
>>
>> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
>> if contents == 'hello':
>> break
>>
>> and the contents of "chunk2" were "hello" then the loop would exit, and
>> "chunk2" would never be closed! Yielding inside a with block, therefore,
>> doesn't make sense and can only lead to obscure bugs.
>
> This is a real problem. (Well, technically the 'with' block's __exit__
> function *will* eventually close the file, when the generator is
> garbage-collected – see PEP 342 for details – but this is not exactly
> satisfying, because the whole purpose of the 'with' block is to close
> the file *without* relying on the garbage collector.)

PEP 342 guarantees that *if* a generator is garbage collected its
.close() method will be called which would in turn trigger __exit__()
for any active context managers in the generator. However PEP 342 does
not guarantee that the generator would be garbage collected. As you
noted in PEP 533:

In Python implementations that do not use reference counting
(e.g. PyPy, Jython), calls to __del__ may be arbitrarily delayed...

However this statement does not go far enough. It is not guaranteed
that __del__ will be called *at all* under other implementations. An
example to test this is:

$ cat gencm.py

class CM:
def __enter__(self):
print("Entering")
return self
def __exit__(self, *args):
print("Exiting")

def generator():
with CM():
yield 1
yield 2
yield 3

g = generator()

def f():
for x in g:
break  # Or return

f()

print("End of program")

$ python3 gencm.py
Entering
End of program
Exiting

$ pypy --version
Python 2.7.2 (1.8+dfsg-2, Feb 19 2012, 19:18:08)
[PyPy 1.8.0 with GCC 4.6.2]

$ pypy gencm.py
Entering
End of program

(I don't actually have pypy to hand right now so I'm copying this from here:
https://www.mail-archive.com/tutor@python.org/msg70961.html)

What the above shows is that for this example:
1) Under CPython __exit__ is called by __del__ at process exit after
every line of Python code has finished.
2) Under PyPy __exit__ was not called at any point

That's not a bug in PyPy: Python the language makes very few
guarantees about garbage collection.

> Unfortunately, your proposal for solving it is a non-starter -- there
> are lots of cases where 'yield' inside a 'with' block is not only
> used, but is clearly the right thing to do. A notable one is when
> defining a next contextmanager in terms of a pre-existing
> contextmanager:
>
> @contextmanager
> def tempfile():
> # This is an insecure way of making a temp file but good enough
> for an example
> tempname = pick_random_filename()
> with open(tempname, "w") as f:
> yield f

This is the only example I can think of where yielding from a with
statement is the right thing to do. Really this comes from the way
that the contextmanager is abusing syntax though. It takes a bunch of
strange machinery to make this work as it does:

https://github.com/python/cpython/blob/3.7/Lib/contextlib.py#L116

> One small step that might be doable would be to start issuing
> ResourceWarning whenever a generator that was suspended inside a
> 'with' or 'try' block is GC'ed.

This seems like a great idea to me.

--
Oscar
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] File format for automatic and manual tests

2018-08-08 Thread Barry Scott
On Tuesday, 7 August 2018 22:57:51 BST Victor Porton wrote:
> This is an idea of a new PEP.
> 
> I propose to create a portable file format which will list command line
> options to run Python scripts with dual purpose:

At the moment I solve this problem with various solutions, depending on 
requirements.

* use python unittest
* add a test target to a makefile.
* write a bash script to run the tests and diff output if required
* on windows do the same with CMD scripts
* use python to run python in a subprocess that run the tests.

Given all these ways to solve the problem what extra are you looking for?

Barry



___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] File format for automatic and manual tests

2018-08-08 Thread Paul Moore
On Wed, 8 Aug 2018 at 00:35, Victor Porton  wrote:
>
> > But I see no reason why this has anything to do with the Python standard
> > library. Can you explain why you want to distribute an experimental
> > file format in the Python std lib?
>
> As I pointed out, I want this format to become common (standardized!)
> for different IDEs and other development environment.

This strikes me as *absolutely* something that should be promoted
outside of the stdlib, as a 3rd party project, and once it's
established as a commonly used and accepted standard, only then
propose that the stdlib offer support for it (if that's even needed at
that point).

Trying to promote a standard by making it "official" and then
encouraging tools to accept it "because it's the official standard"
seems like it's doing things backwards, to me at least.

Paul
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Stephen J. Turnbull
Ken Hilton writes:

 > Can you spot the problem? The "with open(filename)" statement is supposed
 > to ensure that the file object is disposed of properly. However, the "yield
 > f.read()" statement suspends execution within the with block, so if this
 > happened:
 > 
 > for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
 > if contents == 'hello':
 > break
 > 
 > and the contents of "chunk2" were "hello" then the loop would exit, and
 > "chunk2" would never be closed! Yielding inside a with block, therefore,
 > doesn't make sense and can only lead to obscure bugs.

You have one example of a bad outcome.  That hardly shows that *no*
yield in a with block makes sense.  On the contrary Chris A's
suggestion that a function might yield multiple times within one
context manager seems valid, although it might be bad practice.

There's another possibility.  Context managers have general
initialize-operate-finalize semantics.  True, they *often* have the
simple form "return the value of *expr* bound to *var* at entry, clean
up the contents of *var* at exit".  But I find it easy to imagine that
there are situations where the __enter__ method is complex and the
__exit__ method is a no-op.  In that case the problem you identify
can't happen, and yielding inside such a context manager would be
harmless.  A third possibility is that the __exit__ method has
semantics like the "else" of "while ... else", where the "normal" use
case involves a break, but occasionally the input is exhausted, and
some "default" action needs to be taken.

I grant that I don't have examples of the latter (after all, writing
context managers is a relatively rare activity, and the resource
acquisition-and-release paradigm is prominent).  But Chris's point
about line-oriented generators seems both plausible and common.

His analysis of why the risk is small also seems plausible, so I would
say this problematic case (and it is problematic) is covered by the
"consenting adults" principle.

Steve
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Nathaniel Smith
On Tue, Aug 7, 2018 at 11:14 PM, Ken Hilton  wrote:
> This mostly springs off of a comment I saw in some thread.
>
> The point of a with statement is that it ensures that some resource will be
> disposed of, yes? For example, this:
>
> with open(filename) as f:
> contents = f.read()
>
> is better than this:
>
> contents = open(filename).read()
>
> because the former definitely closes the file while the latter relies on
> garbage collection?
>
> The point of a yield expression is to suspend execution. This is nice for
> efficient looping because instead of having to hold all results in memory,
> each result can be consumed immediately, yes? Therefore this:
>
> def five_to_one():
> for i in range(4):
> yield 5 - i
>
> is better than this:
>
> def five_to_one():
> result = []
> for i in range(4):
> result.append(5 - i)
> return result
>
> because the former suspends execution of "five_to_one" while the latter
> holds all five results in memory?
>
> Now, let's take a look at the following scenario:
>
> def read_multiple(*filenames):
> for filename in filenames:
> with open(filename) as f:
> yield f.read()
>
> Can you spot the problem? The "with open(filename)" statement is supposed to
> ensure that the file object is disposed of properly. However, the "yield
> f.read()" statement suspends execution within the with block, so if this
> happened:
>
> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
> if contents == 'hello':
> break
>
> and the contents of "chunk2" were "hello" then the loop would exit, and
> "chunk2" would never be closed! Yielding inside a with block, therefore,
> doesn't make sense and can only lead to obscure bugs.

This is a real problem. (Well, technically the 'with' block's __exit__
function *will* eventually close the file, when the generator is
garbage-collected – see PEP 342 for details – but this is not exactly
satisfying, because the whole purpose of the 'with' block is to close
the file *without* relying on the garbage collector.)

Unfortunately, your proposal for solving it is a non-starter -- there
are lots of cases where 'yield' inside a 'with' block is not only
used, but is clearly the right thing to do. A notable one is when
defining a next contextmanager in terms of a pre-existing
contextmanager:

@contextmanager
def tempfile():
# This is an insecure way of making a temp file but good enough
for an example
tempname = pick_random_filename()
with open(tempname, "w") as f:
yield f

Here are some links for previous discussions around these kinds of
issues, none of which have really gone anywhere but might help you get
a sense of the landscape of options:

https://www.python.org/dev/peps/pep-0533/
https://www.python.org/dev/peps/pep-0521/
https://www.python.org/dev/peps/pep-0568/
https://github.com/python-trio/trio/issues/264

One small step that might be doable would be to start issuing
ResourceWarning whenever a generator that was suspended inside a
'with' or 'try' block is GC'ed.

-n

-- 
Nathaniel J. Smith -- https://vorpus.org
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Chris Angelico
On Wed, Aug 8, 2018 at 5:05 PM, Barry Scott  wrote:
> But so long as you do not leak the generator the file will be closed
> immediately after the loop as the ref count of the generater hits 0.

Technically that's not guaranteed (since refcounts aren't a language
feature), but if you're using this generator somewhere and want to be
able to force it to close its files, all you have to do is close the
generator.

ChrisA
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Barry Scott
On Wednesday, 8 August 2018 07:14:47 BST Ken Hilton wrote:
> Now, let's take a look at the following scenario:
> 
> def read_multiple(*filenames):
> for filename in filenames:
> with open(filename) as f:
> yield f.read()

In this particular case you can fix the code:

def read_multiple(*filenames):
for filename in filenames:
with open(filename) as f:
result = f.read()
yield result

Of course if you used readline() and not read() the problem returns.

But so long as you do not leak the generator the file will be closed 
immediately after the loop as the ref count of the generater hits 0.

Barry



___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Jacco van Dorp
I don't think this is a major problem. In this case, the file will be
closed when the generator is garbage collected. So you'd also have to
leak the generator to actually get this problem. And if leaking
generators won't harm your application, neither will leaking the
occasional file handle.

Also, I think "Yielding inside a with block, therefore, doesn't make
sense and can only lead to obscure bugs." is somewhat of an
overstatement.

Also, the problem isn't the with block. The cause of the "issue" you
describe is that a with statement is a try...finally in disguise. With
the finally clause being guaranteed to be executed. IIRC, but I can't
remember where, I remember a statement from guide referencing this,
and calling it an issue that any fix will cause far more pain than the
issue ever will. Or something to that point.

2018-08-08 8:32 GMT+02:00 Chris Angelico :
> On Wed, Aug 8, 2018 at 4:14 PM, Ken Hilton  wrote:
>> This mostly springs off of a comment I saw in some thread.
>>
>> The point of a with statement is that it ensures that some resource will be
>> disposed of, yes? For example, this:
>>
>> with open(filename) as f:
>> contents = f.read()
>>
>> is better than this:
>>
>> contents = open(filename).read()
>>
>> because the former definitely closes the file while the latter relies on
>> garbage collection?
>
> Technically, the 'with' statement ensures that the file will be closed
> before the line of code following it is run. So in this example:
>
> with open(filename, "w") as f:
> f.write(...)
> os.rename(filename, target_filename)
>
> you have a guarantee that the file is closed prior to the rename call.
>
>> Now, let's take a look at the following scenario:
>>
>> def read_multiple(*filenames):
>> for filename in filenames:
>> with open(filename) as f:
>> yield f.read()
>>
>> Can you spot the problem? The "with open(filename)" statement is supposed to
>> ensure that the file object is disposed of properly. However, the "yield
>> f.read()" statement suspends execution within the with block, so if this
>> happened:
>>
>> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
>> if contents == 'hello':
>> break
>>
>> and the contents of "chunk2" were "hello" then the loop would exit, and
>> "chunk2" would never be closed! Yielding inside a with block, therefore,
>> doesn't make sense and can only lead to obscure bugs.
>
> This is only a problem if you consider it to be one. The value of the
> 'with' statement is not destroyed; for example, you're capped at _one_
> open file (whereas without the context manager, it's entirely possible
> for file-open in a loop to leak a large number of handles).
>
>> I believe all possible cases where one would yield inside a context manager
>> can be covered by saving anything required from the context manager and then
>> yielding the results outside. Therefore, I propose making a "yield" inside a
>> with block become a SyntaxError.
>
> What about this:
>
> def read_words(*filenames):
>  for filename in filenames:
>  with open(filename) as f:
>  for line in f:
>  yield from line.split()
>
> It'll read from a series of files and yield individual words (ignoring
> annoying little things like punctuation and hyphenation for the sake
> of simplicity). You are assuming that every yield-inside-with is a
> *single* yield.
>
>> This means the first "read_multiple" definition I presented will become
>> illegal and fail at compile-time. However, it is still legal to define a
>> generator inside a with block:
>>
>> def pass_file_chars(oldfunc):
>> with open('secretfile') as f:
>> contents = f.read()
>> @functools.wraps
>> def newfunc(*args, **kwargs):
>> for char in contents:
>> yield oldfunc(char, *args, **kwargs)
>> return newfunc
>>
>> This is probably a bad example, but I hope it still explains why it should
>> be legal to define generators in context managers - as long as the with
>> block serves its purpose correctly, everything else should still work
>> normally.
>
> I have no idea what this is supposed to be doing, nor why you're
> defining the function this way. Perhaps a better example will
> illustrate your point more clearly?
>
>> For those concerned about backwards compatibility: I believe that those who
>> attempt to yield inside a context manager will already discover that results
>> are undefined when doing so; this will simply make it more obvious that
>> suspending execution in a with block is not meant to happen, and convert
>> undefined behavior into a straight-up SyntaxError.
>
> The current results are most certainly not undefined, and you're
> attempting to fix something which is not a problem.
>
> If you really find yourself running into situations like this, program
> your linter to warn you when you 

Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Chris Angelico
On Wed, Aug 8, 2018 at 4:14 PM, Ken Hilton  wrote:
> This mostly springs off of a comment I saw in some thread.
>
> The point of a with statement is that it ensures that some resource will be
> disposed of, yes? For example, this:
>
> with open(filename) as f:
> contents = f.read()
>
> is better than this:
>
> contents = open(filename).read()
>
> because the former definitely closes the file while the latter relies on
> garbage collection?

Technically, the 'with' statement ensures that the file will be closed
before the line of code following it is run. So in this example:

with open(filename, "w") as f:
f.write(...)
os.rename(filename, target_filename)

you have a guarantee that the file is closed prior to the rename call.

> Now, let's take a look at the following scenario:
>
> def read_multiple(*filenames):
> for filename in filenames:
> with open(filename) as f:
> yield f.read()
>
> Can you spot the problem? The "with open(filename)" statement is supposed to
> ensure that the file object is disposed of properly. However, the "yield
> f.read()" statement suspends execution within the with block, so if this
> happened:
>
> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
> if contents == 'hello':
> break
>
> and the contents of "chunk2" were "hello" then the loop would exit, and
> "chunk2" would never be closed! Yielding inside a with block, therefore,
> doesn't make sense and can only lead to obscure bugs.

This is only a problem if you consider it to be one. The value of the
'with' statement is not destroyed; for example, you're capped at _one_
open file (whereas without the context manager, it's entirely possible
for file-open in a loop to leak a large number of handles).

> I believe all possible cases where one would yield inside a context manager
> can be covered by saving anything required from the context manager and then
> yielding the results outside. Therefore, I propose making a "yield" inside a
> with block become a SyntaxError.

What about this:

def read_words(*filenames):
 for filename in filenames:
 with open(filename) as f:
 for line in f:
 yield from line.split()

It'll read from a series of files and yield individual words (ignoring
annoying little things like punctuation and hyphenation for the sake
of simplicity). You are assuming that every yield-inside-with is a
*single* yield.

> This means the first "read_multiple" definition I presented will become
> illegal and fail at compile-time. However, it is still legal to define a
> generator inside a with block:
>
> def pass_file_chars(oldfunc):
> with open('secretfile') as f:
> contents = f.read()
> @functools.wraps
> def newfunc(*args, **kwargs):
> for char in contents:
> yield oldfunc(char, *args, **kwargs)
> return newfunc
>
> This is probably a bad example, but I hope it still explains why it should
> be legal to define generators in context managers - as long as the with
> block serves its purpose correctly, everything else should still work
> normally.

I have no idea what this is supposed to be doing, nor why you're
defining the function this way. Perhaps a better example will
illustrate your point more clearly?

> For those concerned about backwards compatibility: I believe that those who
> attempt to yield inside a context manager will already discover that results
> are undefined when doing so; this will simply make it more obvious that
> suspending execution in a with block is not meant to happen, and convert
> undefined behavior into a straight-up SyntaxError.

The current results are most certainly not undefined, and you're
attempting to fix something which is not a problem.

If you really find yourself running into situations like this, program
your linter to warn you when you do it.

ChrisA
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Elazar
On Tue, Aug 7, 2018 at 11:16 PM Ken Hilton  wrote:

> ...
> Now, let's take a look at the following scenario:
>
> def read_multiple(*filenames):
> for filename in filenames:
> with open(filename) as f:
> yield f.read()
>
> Can you spot the problem? The "with open(filename)" statement is supposed
> to ensure that the file object is disposed of properly. However, the "yield
> f.read()" statement suspends execution within the with block, so if this
> happened:
>
> for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
> if contents == 'hello':
> break
>
> and the contents of "chunk2" were "hello" then the loop would exit, and
> "chunk2" would never be closed! Yielding inside a with block, therefore,
> doesn't make sense and can only lead to obscure bugs.
>

Instead of disallowing code, this is a case for allowing with expressions:

def read_multiple(*filenames):
for filename in filenames:
yield (f.read() with open(filename) as f)

Elazar
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Abdur-Rahmaan Janhangeer
i think a SyntaxError won't be appropriate as it is valid syntax as the
lexer finds nothing wrong

it falls more i think like out of index errors and the like, a
ContextManagerError ?

else, a request to add headings like

EXAMPLES
=

in your discussion

yours,

-- 
Abdur-Rahmaan Janhangeer
https://github.com/abdur-rahmaanj
Mauritius
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Make "yield" inside a with statement a SyntaxError

2018-08-08 Thread Ken Hilton
This mostly springs off of a comment I saw in some thread.

The point of a with statement is that it ensures that some resource will be
disposed of, yes? For example, this:

with open(filename) as f:
contents = f.read()

is better than this:

contents = open(filename).read()

because the former definitely closes the file while the latter relies on
garbage collection?

The point of a yield expression is to suspend execution. This is nice for
efficient looping because instead of having to hold all results in memory,
each result can be consumed immediately, yes? Therefore this:

def five_to_one():
for i in range(4):
yield 5 - i

is better than this:

def five_to_one():
result = []
for i in range(4):
result.append(5 - i)
return result

because the former suspends execution of "five_to_one" while the latter
holds all five results in memory?

Now, let's take a look at the following scenario:

def read_multiple(*filenames):
for filename in filenames:
with open(filename) as f:
yield f.read()

Can you spot the problem? The "with open(filename)" statement is supposed
to ensure that the file object is disposed of properly. However, the "yield
f.read()" statement suspends execution within the with block, so if this
happened:

for contents in read_multiple('chunk1', 'chunk2', 'chunk3'):
if contents == 'hello':
break

and the contents of "chunk2" were "hello" then the loop would exit, and
"chunk2" would never be closed! Yielding inside a with block, therefore,
doesn't make sense and can only lead to obscure bugs.
The proper way to define the "read_multiple" function would be like so:

def read_multiple(*filenames):
for filename in filenames:
with open(filename) as f:
contents = f.read()
yield contents

Save the contents in a variable somewhere, then yield the variable, instead
of suspending execution within a context manager.

I believe all possible cases where one would yield inside a context manager
can be covered by saving anything required from the context manager and
then yielding the results outside. Therefore, I propose making a "yield"
inside a with block become a SyntaxError.

This means the first "read_multiple" definition I presented will become
illegal and fail *at compile-time*. However, it is still legal to define a
generator inside a with block:

def pass_file_chars(oldfunc):
with open('secretfile') as f:
contents = f.read()
@functools.wraps
def newfunc(*args, **kwargs):
for char in contents:
yield oldfunc(char, *args, **kwargs)
return newfunc

This is probably a bad example, but I hope it still explains why it should
be legal to define generators in context managers - as long as the with
block serves its purpose correctly, everything else should still work
normally.

For those concerned about backwards compatibility: I believe that those who
attempt to yield inside a context manager will already discover that
results are undefined when doing so; this will simply make it more obvious
that suspending execution in a with block is not meant to happen, and
convert undefined behavior into a straight-up SyntaxError.

What are your thoughts?

Sharing,
Ken Hilton;
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/