Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-04-11 Thread MRAB

On 2018-04-11 04:15, Mike Miller wrote:

If anyone is interested I came across this same subject on a blog post and
discussion on HN today:

- https://www.hillelwayne.com/post/equals-as-assignment/


It says "BCPL also introduced braces as a means of defining blocks.".

That bit is wrong, unless "braces" is being used as a generic term. BCPL 
used $( and $).

___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-10 Thread Chris Angelico
On Wed, Apr 11, 2018 at 1:15 PM, Mike Miller  wrote:
> If anyone is interested I came across this same subject on a blog post and
> discussion on HN today:
>
> - https://www.hillelwayne.com/post/equals-as-assignment/
> - https://news.ycombinator.com/item?id=16803874

Those people who say "x = x + 1" makes no sense... do they also get
confused by the fact that you can multiply a string by a number?
Programming is not algebra. The ONLY reason that "x = x + 1" can fail
to make sense is if you start by assuming that there is no such thing
as time. That's the case in algebra, but it simply isn't true in
software. Functional programming languages are closer to algebra than
imperative languages are, but that doesn't mean they _are_ algebraic,
and they go to great lengths to lie about how you can have
side-effect-free side effects and such.

Fortunately, Python is not bound by such silly rules, and can do
things because they are useful for real-world work. Thus the question
of ":=" vs "=" vs "==" vs "===" comes down to what is actually worth
doing, not what would look tidiest to someone who is trying to
represent a mathematician's blackboard in ASCII.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-10 Thread Mike Miller
If anyone is interested I came across this same subject on a blog post and 
discussion on HN today:


- https://www.hillelwayne.com/post/equals-as-assignment/
- https://news.ycombinator.com/item?id=16803874


On 2018-04-02 15:03, Guido van Rossum wrote:
IIRC Algol-68 (the lesser-known, more complicated version) used 'int x = 0;' to 
declare a constant and 'int x := 0;' to declare a variable. And there was a lot 
more to it; see https://en.wikipedia.org/wiki/ALGOL_68#mode:_Declarations. I'm 
guessing Go reversed this because they want '=' to be the common assignment 
(whereas in Algol-68 the common assignment was ':=').


___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-09 Thread Nick Coghlan
On 9 April 2018 at 01:01, Steven D'Aprano  wrote:
> On Sun, Apr 08, 2018 at 09:25:33PM +1000, Nick Coghlan wrote:
>
>> I was writing a new stdlib test case today, and thinking about how I
>> might structure it differently in a PEP 572 world, and realised that a
>> situation the next version of the PEP should discuss is this one:
>>
>> # Dict display
>> data = {
>> key_a: 1,
>> key_b: 2,
>> key_c: 3,
>> }
>>
>> # Set display with local name bindings
>> data = {
>> local_a := 1,
>> local_b := 2,
>> local_c := 3,
>>}
>
> I don't understand the point of these examples. Sure, I guess they would
> be legal, but unless you're actually going to use the name bindings,
> what's the point in defining them?

That *would* be the point.

In the case where it occurred to me, the actual code I'd written
looked like this:

   curdir_import = ""
   curdir_relative = os.curdir
   curdir_absolute = os.getcwd()
   all_spellings = [curdir_import, curdir_relative, curdir_absolute]

(Since I was testing the pydoc CLI's sys.path manipulation, and wanted
to cover all the cases).


>> I don't think this is bad (although the interaction with dicts is a
>> bit odd), and I don't think it counts as a rationale either, but I do
>> think the fact that it becomes possible should be noted as an outcome
>> arising from the "No sublocal scoping" semantics.
>
> If we really wanted to keep the sublocal scoping, we could make
> list/set/dict displays their own scope too.
>
> Personally, that's the only argument for sublocal scoping that I like
> yet: what happens inside a display should remain inside the display, and
> not leak out into the function.
>
> So that has taken me from -1 on sublocal scoping to -0.5 if it applies
> to displays.

Inflicting the challenges that comprehensions have at class scope on
all container displays wouldn't strike me as a desirable outcome (plus
there's also the problem that full nested scopes are relatively
expensive at runtime).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Steve Dower
# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

Isn’t this a set display with local assignments and type annotations? :o)

(I’m -1 on all of these ideas, btw. None help readability for me, and I read 
much more code than I write.)

Top-posted from my Windows phone

From: Nick Coghlan
Sent: Sunday, April 8, 2018 6:27
To: Chris Angelico
Cc: python-ideas
Subject: Re: [Python-ideas] PEP 572: Statement-Local Name Bindings,take three!

On 23 March 2018 at 20:01, Chris Angelico <ros...@gmail.com> wrote:
> Apologies for letting this languish; life has an annoying habit of
> getting in the way now and then.
>
> Feedback from the previous rounds has been incorporated. From here,
> the most important concern and question is: Is there any other syntax
> or related proposal that ought to be mentioned here? If this proposal
> is rejected, it should be rejected with a full set of alternatives.

I was writing a new stdlib test case today, and thinking about how I
might structure it differently in a PEP 572 world, and realised that a
situation the next version of the PEP should discuss is this one:

# Dict display
data = {
key_a: 1,
key_b: 2,
key_c: 3,
}

# Set display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# List display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

# Dict display with local key name bindings
data = {
local_a := key_a: 1,
local_b := key_b: 2,
local_c := key_c: 3,
}

I don't think this is bad (although the interaction with dicts is a
bit odd), and I don't think it counts as a rationale either, but I do
think the fact that it becomes possible should be noted as an outcome
arising from the "No sublocal scoping" semantics.

Cheers,
Nick.

P.S. The specific test case is one where I want to test the three
different ways of spelling "the current directory" in some sys.path
manipulation code (the empty string, os.curdir, and os.getcwd()), and
it occurred to me that a version of PEP 572 that omits the sublocal
scoping concept will allow inline naming of parts of data structures
as you define them.


-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Guido van Rossum
On Sun, Apr 8, 2018 at 8:01 AM, Steven D'Aprano  wrote:

> If we really wanted to keep the sublocal scoping, we could make
> list/set/dict displays their own scope too.
>
> Personally, that's the only argument for sublocal scoping that I like
> yet: what happens inside a display should remain inside the display, and
> not leak out into the function.
>

That sounds like a reasonable proposal that we could at least consider. But
I think it will not fly. Presumably it doesn't apply to tuple displays,
because of reasonable examples like ((a := f(), a+1), a+2), and because it
would create an ugly discontinuity between (a := f()) and (a := f(),). But
then switching between [a := f(), a] and (a := f(), a) would create a
discontinuity.

For comprehensions and generator expressions there is no such discontinuity
in the new proposal, since these *already* introduce their own scope.

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Steven D'Aprano
On Sun, Apr 08, 2018 at 09:25:33PM +1000, Nick Coghlan wrote:

> I was writing a new stdlib test case today, and thinking about how I
> might structure it differently in a PEP 572 world, and realised that a
> situation the next version of the PEP should discuss is this one:
> 
> # Dict display
> data = {
> key_a: 1,
> key_b: 2,
> key_c: 3,
> }
> 
> # Set display with local name bindings
> data = {
> local_a := 1,
> local_b := 2,
> local_c := 3,
>}

I don't understand the point of these examples. Sure, I guess they would 
be legal, but unless you're actually going to use the name bindings, 
what's the point in defining them?


data = {
1,
(spam := complex_expression),
spam+1,
spam*2,
}

which I think is cleaner than the existing alternative of defining spam 
outside of the set.

And for dicts:

d = {
   'key': 'value',
   (spam := calculated_key): (eggs := calculated_value),
   spam.lower(): eggs.upper(),
   }


> I don't think this is bad (although the interaction with dicts is a
> bit odd), and I don't think it counts as a rationale either, but I do
> think the fact that it becomes possible should be noted as an outcome
> arising from the "No sublocal scoping" semantics.

If we really wanted to keep the sublocal scoping, we could make 
list/set/dict displays their own scope too.

Personally, that's the only argument for sublocal scoping that I like 
yet: what happens inside a display should remain inside the display, and 
not leak out into the function.

So that has taken me from -1 on sublocal scoping to -0.5 if it applies 
to displays.



-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Nick Coghlan
On 23 March 2018 at 20:01, Chris Angelico  wrote:
> Apologies for letting this languish; life has an annoying habit of
> getting in the way now and then.
>
> Feedback from the previous rounds has been incorporated. From here,
> the most important concern and question is: Is there any other syntax
> or related proposal that ought to be mentioned here? If this proposal
> is rejected, it should be rejected with a full set of alternatives.

I was writing a new stdlib test case today, and thinking about how I
might structure it differently in a PEP 572 world, and realised that a
situation the next version of the PEP should discuss is this one:

# Dict display
data = {
key_a: 1,
key_b: 2,
key_c: 3,
}

# Set display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# List display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

# Dict display with local key name bindings
data = {
local_a := key_a: 1,
local_b := key_b: 2,
local_c := key_c: 3,
}

I don't think this is bad (although the interaction with dicts is a
bit odd), and I don't think it counts as a rationale either, but I do
think the fact that it becomes possible should be noted as an outcome
arising from the "No sublocal scoping" semantics.

Cheers,
Nick.

P.S. The specific test case is one where I want to test the three
different ways of spelling "the current directory" in some sys.path
manipulation code (the empty string, os.curdir, and os.getcwd()), and
it occurred to me that a version of PEP 572 that omits the sublocal
scoping concept will allow inline naming of parts of data structures
as you define them.


-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-02 Thread Mike Miller

Interesting, thanks.

On 2018-04-02 15:03, Guido van Rossum wrote:
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-02 Thread Guido van Rossum
IIRC Algol-68 (the lesser-known, more complicated version) used 'int x =
0;' to declare a constant and 'int x := 0;' to declare a variable. And
there was a lot more to it; see
https://en.wikipedia.org/wiki/ALGOL_68#mode:_Declarations. I'm guessing Go
reversed this because they want '=' to be the common assignment (whereas in
Algol-68 the common assignment was ':=').

My current thinking about Python is that if we're doing this, '=' and ':='
will mean the same thing but inside an expression you must use ':='. Chris,
Nick and I are working out some details off-list.

On Mon, Apr 2, 2018 at 1:51 PM, Mike Miller 
wrote:

> Yes, I first came across := when learning (Turbo) Pascal in the early 90's.
>
> However golang managed to screw it up—it only works there as a "short
> declaration AND assignment" operator.  You can't use it twice on the same
> variable!  Boggles the mind how experienced designers came up with that
> one. ;-)  Maybe Algol did it that way?  (before my time)
>
> I found Pascal's syntax, := for assignment, = and <>, for tests about
> close to perfect in ease of learning/comprehension as it gets, from someone
> who studied math before C anyway.
>
> -Mike
>
>
>
> On 2018-03-30 12:04, Nikolaus Rath wrote:
> ___
> Python-ideas mailing list
> Python-ideas@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>



-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-02 Thread Mike Miller

Yes, I first came across := when learning (Turbo) Pascal in the early 90's.

However golang managed to screw it up—it only works there as a "short 
declaration AND assignment" operator.  You can't use it twice on the same 
variable!  Boggles the mind how experienced designers came up with that one. ;-) 
 Maybe Algol did it that way?  (before my time)


I found Pascal's syntax, := for assignment, = and <>, for tests about close to 
perfect in ease of learning/comprehension as it gets, from someone who studied 
math before C anyway.


-Mike


On 2018-03-30 12:04, Nikolaus Rath wrote:
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-30 Thread Guido van Rossum
On Fri, Mar 30, 2018 at 12:04 PM, Nikolaus Rath  wrote:

> On Mar 25 2018, Guido van Rossum  public.gmane.org> wrote:
> > I gotta say I'm warming up to := in preference over 'as', *if* we're
> going
> > to do this at all (not a foregone conclusion at all).
>
> I'm surprised that no one has mentioned it yet, so as a quick datapoint:
> Go also uses := for assignment, so there's some precedent.
>

It's irrelevant, because Go's solution for inline assignment is entirely
different. (And there was no question that := is commonly used for
assignment -- just look it up on Wikipedia.)

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-30 Thread Nikolaus Rath
On Mar 25 2018, Guido van Rossum 
 wrote:
> I gotta say I'm warming up to := in preference over 'as', *if* we're going
> to do this at all (not a foregone conclusion at all).

I'm surprised that no one has mentioned it yet, so as a quick datapoint:
Go also uses := for assignment, so there's some precedent.


Best,
-Nikolaus

-- 
GPG Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F

 »Time flies like an arrow, fruit flies like a Banana.«
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread BrenBarn

On 03/23/2018 03:01 AM, Chris Angelico wrote:

Apologies for letting this languish; life has an annoying habit of
getting in the way now and then.


	My simple response to all of this is that it's not worth it.  Each new 
example convinces me more and more that in almost every case, sublocal 
assignments DECREASE readability as long as they occur inline.  If the 
statement is very simple, the sublocal assignments make it complex.  If 
it is complex, they do not aid in seeing parallelism between different 
pieces that reuse the same value, because the sublocal assignment itself 
creates an asymmetry.


	The only alternatives that I see as increasing readability are the 
"rejected" alternatives in which the sublocal assignment is moved "out 
of order" so that all references to it look the same and are separated 
from the (single) assignment --- i.e., the variants of the form "x = a+b 
with a='foo', b='bar'".


	(I think someone already mentioned this, but these variants, even if 
rejected, probably shouldn't be placed under the header of 
"special-casing comprehensions".  Extracting the assignment to a 
with-clause makes sense outside of comprehensions too.  It would make 
more sense to label them as "out of order" or "non-inline" or perhaps 
"cleft assignment", by analogy with cleft constructions in natural 
language.)


___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Guido van Rossum
This thread is dead.

On Tue, Mar 27, 2018 at 5:40 PM, Rob Cliffe via Python-ideas <
python-ideas@python.org> wrote:

>
>
> On 28/03/2018 01:19, Steven D'Aprano wrote:
>
>> On Wed, Mar 28, 2018 at 12:08:24AM +0100, Rob Cliffe via Python-ideas
>> wrote:
>>
>>> On 27/03/2018 16:22, Guido van Rossum wrote:
>>>
 The standard reply here is that if you can't tell at a glance whether
 that's the case, your code is too complex. The Zen of Python says
 "Namespaces are one honking great idea -- let's do more of those!" and
 in this case that means refactor into smaller namespaces, i.e.
 functions/methods.

 This is not always satisfactory.  If your for-loop uses 20
>>> already-defined-locals, do you want to refactor it into a function with
>>> 20 parameters?
>>>
>> The standard reply here is that if your for-loop needs 20 locals, your
>> function is horribly over-complex and you may need to rethink your
>> design.
>>
>> And if you don't think "20 locals" is too many, okay, how about 50? 100?
>> 1000? At some point we'll all agree that the function is too complex.
>>
>> We don't have an obligation to solve every problem of excess complexity,
>> especially when the nominal solution involves adding complexity
>> elsewhere.
>>
>> For 25 years, the solution to complex functions in Python has been to
>> refactor or simplify them. That strategy has worked well in practice,
>> not withstanding your hypothetical function.
>>
>> If you genuinely do have a function that is so highly coupled with so
>> many locals that it is hard to refactor, then you have my sympathy but
>> we have no obligation to add a band-aid for it to the language.
>>
> It's a fact of life that some tasks *are* complicated.  I daresay most
> aren't, or don't need to be, but some are.
>
>>
>> Putting the loop variable in its own scope doesn't do anything about the
>> real problem: you have a loop that needs to work with twenty other local
>> variables. Any other modification to the loop will run into the same
>> problem: you have to check the rest of the function to ensure you're not
>> clobbering one of the twenty other variables. Special-casing the loop
>> variable seems hardly justified.
>>
>> If there is a justification for introducing sub-local scoping, then I
>> think it needs to be something better than pathologically over-complex
>> functions.
>>
>>
>> But putting the loop variable in its own scope solves one problem: it
> ensures that the variable is confined to that loop, and you don't have to
> worry about whether a variable of the same name occurs elsewhere in your
> function.  In other words it increases local transparency (I'm not sure
> that's the right phrase, but I'm struggling to bring a more appropriate one
> to mind) and hence increases readability.
> (I understand your point about being able to inspect the for-loop variable
> after the for-loop has terminated - I've probably done it myself - but it's
> a matter of opinion whether that convenience outweighs the cleanliness of
> confining the for-variable's scope.)
> Regards
> Rob Cliffe
>
> ___
> Python-ideas mailing list
> Python-ideas@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>



-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Rob Cliffe via Python-ideas



On 28/03/2018 01:19, Steven D'Aprano wrote:

On Wed, Mar 28, 2018 at 12:08:24AM +0100, Rob Cliffe via Python-ideas wrote:

On 27/03/2018 16:22, Guido van Rossum wrote:

The standard reply here is that if you can't tell at a glance whether
that's the case, your code is too complex. The Zen of Python says
"Namespaces are one honking great idea -- let's do more of those!" and
in this case that means refactor into smaller namespaces, i.e.
functions/methods.


This is not always satisfactory.  If your for-loop uses 20
already-defined-locals, do you want to refactor it into a function with
20 parameters?

The standard reply here is that if your for-loop needs 20 locals, your
function is horribly over-complex and you may need to rethink your
design.

And if you don't think "20 locals" is too many, okay, how about 50? 100?
1000? At some point we'll all agree that the function is too complex.

We don't have an obligation to solve every problem of excess complexity,
especially when the nominal solution involves adding complexity
elsewhere.

For 25 years, the solution to complex functions in Python has been to
refactor or simplify them. That strategy has worked well in practice,
not withstanding your hypothetical function.

If you genuinely do have a function that is so highly coupled with so
many locals that it is hard to refactor, then you have my sympathy but
we have no obligation to add a band-aid for it to the language.
It's a fact of life that some tasks *are* complicated.  I daresay most 
aren't, or don't need to be, but some are.


Putting the loop variable in its own scope doesn't do anything about the
real problem: you have a loop that needs to work with twenty other local
variables. Any other modification to the loop will run into the same
problem: you have to check the rest of the function to ensure you're not
clobbering one of the twenty other variables. Special-casing the loop
variable seems hardly justified.

If there is a justification for introducing sub-local scoping, then I
think it needs to be something better than pathologically over-complex
functions.


But putting the loop variable in its own scope solves one problem: it 
ensures that the variable is confined to that loop, and you don't have 
to worry about whether a variable of the same name occurs elsewhere in 
your function.  In other words it increases local transparency (I'm not 
sure that's the right phrase, but I'm struggling to bring a more 
appropriate one to mind) and hence increases readability.
(I understand your point about being able to inspect the for-loop 
variable after the for-loop has terminated - I've probably done it 
myself - but it's a matter of opinion whether that convenience outweighs 
the cleanliness of confining the for-variable's scope.)

Regards
Rob Cliffe
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Steven D'Aprano
On Wed, Mar 28, 2018 at 12:08:24AM +0100, Rob Cliffe via Python-ideas wrote:
> 
> On 27/03/2018 16:22, Guido van Rossum wrote:

> >The standard reply here is that if you can't tell at a glance whether 
> >that's the case, your code is too complex. The Zen of Python says 
> >"Namespaces are one honking great idea -- let's do more of those!" and 
> >in this case that means refactor into smaller namespaces, i.e. 
> >functions/methods.
> >
> This is not always satisfactory.  If your for-loop uses 20 
> already-defined-locals, do you want to refactor it into a function with 
> 20 parameters?

The standard reply here is that if your for-loop needs 20 locals, your 
function is horribly over-complex and you may need to rethink your 
design.

And if you don't think "20 locals" is too many, okay, how about 50? 100? 
1000? At some point we'll all agree that the function is too complex.

We don't have an obligation to solve every problem of excess complexity, 
especially when the nominal solution involves adding complexity 
elsewhere.

For 25 years, the solution to complex functions in Python has been to 
refactor or simplify them. That strategy has worked well in practice, 
not withstanding your hypothetical function.

If you genuinely do have a function that is so highly coupled with so 
many locals that it is hard to refactor, then you have my sympathy but 
we have no obligation to add a band-aid for it to the language.

Putting the loop variable in its own scope doesn't do anything about the 
real problem: you have a loop that needs to work with twenty other local 
variables. Any other modification to the loop will run into the same 
problem: you have to check the rest of the function to ensure you're not 
clobbering one of the twenty other variables. Special-casing the loop 
variable seems hardly justified.

If there is a justification for introducing sub-local scoping, then I 
think it needs to be something better than pathologically over-complex 
functions.


-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Rob Cliffe via Python-ideas



On 27/03/2018 16:22, Guido van Rossum wrote:
On Tue, Mar 27, 2018 at 7:00 AM, Nick Coghlan > wrote:


On 27 March 2018 at 01:57, Guido van Rossum > wrote:
> On Mon, Mar 26, 2018 at 7:57 AM, Nick Coghlan
> wrote:
>> By contrast, the sublocals idea strives to keep the *lifecycle* impact
>> of naming a subexpression as negligible as possible - while a named
>> subexpression might live a little longer than it used to as an
>> anonymous subexpression (or substantially longer in the case of
>> compound statement headers), it still wouldn't survive past the
end of
>> the statement where it appeared.
>
>
> But this is not new: if you use a for-loop to initialize some
class-level
> structure  you have the same problem. There is also a standard
solution
> (just 'del' it).

Right, but that's annoying, too, and adds "Am I polluting a namespace
I care about?" to something that would ideally be a purely statement
local consideration (and currently is for comprehensions and generator
expressions).


The standard reply here is that if you can't tell at a glance whether 
that's the case, your code is too complex. The Zen of Python says 
"Namespaces are one honking great idea -- let's do more of those!" and 
in this case that means refactor into smaller namespaces, i.e. 
functions/methods.


This is not always satisfactory.  If your for-loop uses 20 
already-defined-locals, do you want to refactor it into a function with 
20 parameters?

Regards
Rob Cliffe
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Cammil Taank
>
> ... From here,
> the most important concern and question is: Is there any other syntax
> or related proposal that ought to be mentioned here?


I am not sure if this is valid, but perhaps this is an alternative syntax
which might be simpler:

``name! expr``

So for example, instead of:

stuff = [[(f(x) as y), x/y] for x in range(5)]

stuff = [[y! f(x), x/y] for x in range(5)]

As far as I can tell there would be no conflicts with the current uses of "!".

One potential source of ambiguity would be in:

x = y! a + b # should y be a or (a + b)?

I think this is solved by requiring the target expression to be
non-greedy. If you want a larger named expression, you can always use
parenthesis. i.e. ``x = y! (z + z)``

I feel brevity and minimised punctuation are important for the
adoption of statement-locals, and personally I feel it reads well. I
also happen to prefer the name preceding the expression, though I
suspect this is quite subjective.

Also, apologies if I have grossly misunderstood something.

Cammil
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Guido van Rossum
On Tue, Mar 27, 2018 at 7:00 AM, Nick Coghlan  wrote:

> On 27 March 2018 at 01:57, Guido van Rossum  wrote:
> > On Mon, Mar 26, 2018 at 7:57 AM, Nick Coghlan 
> wrote:
> >> By contrast, the sublocals idea strives to keep the *lifecycle* impact
> >> of naming a subexpression as negligible as possible - while a named
> >> subexpression might live a little longer than it used to as an
> >> anonymous subexpression (or substantially longer in the case of
> >> compound statement headers), it still wouldn't survive past the end of
> >> the statement where it appeared.
> >
> >
> > But this is not new: if you use a for-loop to initialize some class-level
> > structure  you have the same problem. There is also a standard solution
> > (just 'del' it).
>
> Right, but that's annoying, too, and adds "Am I polluting a namespace
> I care about?" to something that would ideally be a purely statement
> local consideration (and currently is for comprehensions and generator
> expressions).
>

The standard reply here is that if you can't tell at a glance whether
that's the case, your code is too complex. The Zen of Python says
"Namespaces are one honking great idea -- let's do more of those!" and in
this case that means refactor into smaller namespaces, i.e.
functions/methods.

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Kyle Lahnakoski


On 2018-03-23 06:01, Chris Angelico wrote:
> https://www.python.org/dev/peps/pep-0572/
>

A suggestion:

Under the rejected "Special-casing comprehensions", you show
"prefix-local-name-bindings": Name bindings that appear before the loop,
like:

> stuff = [(y, x/y) where y = f(x) for x in range(5)]


Please add mention of rejecting "postfix-local-name-bindings": Name
bindings that happen after the loop.  For example:

> stuff = [(y, x/y) for x in range(5) where y = f(x)]


Since all the same reasoning applies to both prefix and postfix
variations, maybe distinguishing between prefix and postfix can be done
in the last paragraph of "Special-casing comprehensions".

Thank you.
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-27 Thread Nick Coghlan
On 27 March 2018 at 01:57, Guido van Rossum  wrote:
> On Mon, Mar 26, 2018 at 7:57 AM, Nick Coghlan  wrote:
>> By contrast, the sublocals idea strives to keep the *lifecycle* impact
>> of naming a subexpression as negligible as possible - while a named
>> subexpression might live a little longer than it used to as an
>> anonymous subexpression (or substantially longer in the case of
>> compound statement headers), it still wouldn't survive past the end of
>> the statement where it appeared.
>
>
> But this is not new: if you use a for-loop to initialize some class-level
> structure  you have the same problem. There is also a standard solution
> (just 'del' it).

Right, but that's annoying, too, and adds "Am I polluting a namespace
I care about?" to something that would ideally be a purely statement
local consideration (and currently is for comprehensions and generator
expressions).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Tim Peters
[Guido]
> ...
> Not so fast. There's a perfectly reasonable alternative to sublocal scopes
> -- just let it assign to a local variable in the containing scope. That's
> the same as what Python does for for-loop variables.

That's certainly what I would _expect_ if I never read the docs,
conditioned by experience with Python's `for` and embedded assignments
in at least C and Icon.

But I have to confess I already gave up trying to stay up-to-date with
all of Python's _current_ scope rules.  It's not what I want to think
about.  It's easier all around to try not to reuse names in clever
ways to begin with.
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Steven D'Aprano
On Mon, Mar 26, 2018 at 03:33:52PM +0100, Rhodri James wrote:

>  While my crystal ball is cloudy, I can well imagine beginners becoming 
> very confused over which symbol to use in which circumstance, and a lot 
> of swearing when:
> 
>   x := f()
>   if (y = g(x)) is not None:
> h(y)
> 
> results in syntax errors.

I remember as a beginner being terribly confused when writing dicts and 
constantly writing {key=value}. It is part of the learning process, and 
while we shouldn't intentionally make things harder for beginners just 
for the sake of making it harder, we shouldn't necessarily give them 
veto over new features :-)

(I must admit that even now, if I'm tired and distracted I occasionally 
make this same mistake.)

But we also have the opportunity to make things easier for them. I 
presume that the syntax error could diagnose the error and tell them 
how to fix it:

SyntaxError: cannot use := in a stand-alone statement, use =

SyntaxError: cannot use = in an assignment expression, use :=

or similar. Problem solved.


-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Rob Cliffe via Python-ideas



On 26/03/2018 16:57, Guido van Rossum wrote:
On Mon, Mar 26, 2018 at 7:57 AM, Nick Coghlan > wrote:


On 26 March 2018 at 14:34, Guido van Rossum > wrote:
> Not so fast. There's a perfectly reasonable alternative to
sublocal scopes
> -- just let it assign to a local variable in the containing
scope. That's
> the same as what Python does for for-loop variables. Note that for
> comprehensions it still happens to do the right thing (assuming
we interpret
> the comprehension's private local scope to be the containing scope).

I finally remembered one of the original reasons that allowing
embedded assignment to target regular locals bothered me: it makes
named subexpressions public members of the API if you use them at
class or module scope. (I sent an off-list email to Chris about that
yesterday, so the next update to the PEP is going to take it into
account).

Similarly, if you use a named subexpression in a generator or
coroutine and it gets placed in the regular locals() namespace, then
you've now made that reference live for as long as the generator or
coroutine does, even if you never need it again.

By contrast, the sublocals idea strives to keep the *lifecycle* impact
of naming a subexpression as negligible as possible - while a named
subexpression might live a little longer than it used to as an
anonymous subexpression (or substantially longer in the case of
compound statement headers), it still wouldn't survive past the end of
the statement where it appeared.


But this is not new: if you use a for-loop to initialize some 
class-level structure  you have the same problem. There is also a 
standard solution (just 'del' it).
True.  But there is a case for also saying that the for-loop variable's 
scope should have been limited to the for-loop-suite. (Not that it's 
feasible to make that change now, of course.)


If I had a time-machine, I would add an assignment character (probably 
looking something like <- ) to the original ASCII character set.  Then 
"=" means equality - job done.

Actually, probably a right-assignment character ( -> ) as well.

Rob Cliffe
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Guido van Rossum
On Mon, Mar 26, 2018 at 7:57 AM, Nick Coghlan  wrote:

> On 26 March 2018 at 14:34, Guido van Rossum  wrote:
> > Not so fast. There's a perfectly reasonable alternative to sublocal
> scopes
> > -- just let it assign to a local variable in the containing scope. That's
> > the same as what Python does for for-loop variables. Note that for
> > comprehensions it still happens to do the right thing (assuming we
> interpret
> > the comprehension's private local scope to be the containing scope).
>
> I finally remembered one of the original reasons that allowing
> embedded assignment to target regular locals bothered me: it makes
> named subexpressions public members of the API if you use them at
> class or module scope. (I sent an off-list email to Chris about that
> yesterday, so the next update to the PEP is going to take it into
> account).
>
> Similarly, if you use a named subexpression in a generator or
> coroutine and it gets placed in the regular locals() namespace, then
> you've now made that reference live for as long as the generator or
> coroutine does, even if you never need it again.
>
> By contrast, the sublocals idea strives to keep the *lifecycle* impact
> of naming a subexpression as negligible as possible - while a named
> subexpression might live a little longer than it used to as an
> anonymous subexpression (or substantially longer in the case of
> compound statement headers), it still wouldn't survive past the end of
> the statement where it appeared.
>

But this is not new: if you use a for-loop to initialize some class-level
structure  you have the same problem. There is also a standard solution
(just 'del' it).

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Nick Coghlan
On 26 March 2018 at 14:34, Guido van Rossum  wrote:
> Not so fast. There's a perfectly reasonable alternative to sublocal scopes
> -- just let it assign to a local variable in the containing scope. That's
> the same as what Python does for for-loop variables. Note that for
> comprehensions it still happens to do the right thing (assuming we interpret
> the comprehension's private local scope to be the containing scope).

I finally remembered one of the original reasons that allowing
embedded assignment to target regular locals bothered me: it makes
named subexpressions public members of the API if you use them at
class or module scope. (I sent an off-list email to Chris about that
yesterday, so the next update to the PEP is going to take it into
account).

Similarly, if you use a named subexpression in a generator or
coroutine and it gets placed in the regular locals() namespace, then
you've now made that reference live for as long as the generator or
coroutine does, even if you never need it again.

By contrast, the sublocals idea strives to keep the *lifecycle* impact
of naming a subexpression as negligible as possible - while a named
subexpression might live a little longer than it used to as an
anonymous subexpression (or substantially longer in the case of
compound statement headers), it still wouldn't survive past the end of
the statement where it appeared.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Rhodri James

On 26/03/18 02:24, Guido van Rossum wrote:

I gotta say I'm warming up to := in preference over 'as',*if*  we're going
to do this at all (not a foregone conclusion at all).


I have the usual objection to glyphs (hard to look up or get help on), 
but ":=" raises two issues all of its own.


* On the plus side, it looks like some kind of assignment.  People 
reading through the code will not be overly surprised to find it results 
in a name binding.


* On the minus side, it doesn't quite look like an assignment statement. 
 While my crystal ball is cloudy, I can well imagine beginners becoming 
very confused over which symbol to use in which circumstance, and a lot 
of swearing when:


  x := f()
  if (y = g(x)) is not None:
h(y)

results in syntax errors.

I'm inclined to think you want assignment expressions to look unlike 
assignment statements to avoid this sort of confusion.


--
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Steven D'Aprano
On Mon, Mar 26, 2018 at 02:42:32PM +0300, Kirill Balunov wrote:

> I was also thinking about `<-` variant (but with a Haskell in mind), but
> with the current Python rules, it seems that it does not fit:

Ah, of course not, the dreaded unary operator strikes again!

(I was just chatting with Chris about unary operators off-list earlier 
today.)


-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Kirill Balunov
2018-03-26 14:18 GMT+03:00 Steven D'Aprano :

> That was probably my response to Nick:
>
> https://mail.python.org/pipermail/python-ideas/2018-March/049472.html
>
> I compared four possible choices:
>
> target = default if (expression as name) is None else name
> target = default if (name := expression) is None else name
> target = default if (expression -> name) is None else name
> target = default if (name <- expression) is None else name
>

Yes, most likely :)


>
> The two arrow assignment operators <- and -> are both taken from R.
>
>
I was also thinking about `<-` variant (but with a Haskell in mind), but
with the current Python rules, it seems that it does not fit:

>>> x = 10
>>> (x <- 5)
False

With kind regards,
-gdg
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Steven D'Aprano
On Mon, Mar 26, 2018 at 11:14:43AM +0300, Kirill Balunov wrote:
> Hi Chris, would you mind to add this syntactic form `(expr -> var)` to
> alternative syntax section, with the same semantics as `(expr as var)`. It
> seems to me that I've seen this form previously in some thread (can't find
> where), but it does not appear in alt. syntax section.

That was probably my response to Nick:

https://mail.python.org/pipermail/python-ideas/2018-March/049472.html

I compared four possible choices:

target = default if (expression as name) is None else name
target = default if (name := expression) is None else name
target = default if (expression -> name) is None else name
target = default if (name <- expression) is None else name


The two arrow assignment operators <- and -> are both taken from R.

If we go down the sublocal scope path, which I'm not too keen on, then 
Nick's earlier comments convince me that we should avoid "as". In that 
case, my preferences are:

   (best)  ->  :=  <-  as  (worst)

If we just bind to regular locals, then my preferences are:

   (best)  as  ->  :=  <-  (worst)

Preferences are subject to change :-)




-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Chris Angelico
On Mon, Mar 26, 2018 at 7:14 PM, Kirill Balunov  wrote:
> Hi Chris, would you mind to add this syntactic form `(expr -> var)` to
> alternative syntax section, with the same semantics as `(expr as var)`. It
> seems to me that I've seen this form previously in some thread (can't find
> where), but it does not appear in alt. syntax section.

Can do. I'm in the middle of some large edits, and will try to
remember this when I get to that section. If you see another posting
of the PEP and I haven't included it, please remind me and I'll add
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-26 Thread Kirill Balunov
Hi Chris, would you mind to add this syntactic form `(expr -> var)` to
alternative syntax section, with the same semantics as `(expr as var)`. It
seems to me that I've seen this form previously in some thread (can't find
where), but it does not appear in alt. syntax section. As for me this form
has several benefits:
1. Currently it is a SyntaxError
Really there exist some intersection with syntax to annotate function
return type, but it has much smaller impact than `as` variant.
2. This form looks It looks as readable (also the expression comes
first, which appeals to me) as the 'as' variant.
3. It is clearly distinguishable from the usual assignment statement
(which also appeals to me)
Suppose someday someone will want to have a type hint on a local variable
(I think sublocal are safer on this part), then:
```
while (x: int := counter):
do_some_stuff
```
vs
```
while (counter -> x: int):
do_some_stuff
```
Maybe it is too subjective, but the second form looks better for me. taking
in all the аdvantages of the `as` form. Also this '->' form can be extended
to some sort of tuple unpacking. I don't think that tuple unpacking is a
good example, but nevertheless:) And will make further attempts to
introduce ` +:=` impossible.

With kind regards,
-gdg
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Chris Angelico
On Mon, Mar 26, 2018 at 3:34 PM, Guido van Rossum  wrote:
> On Sun, Mar 25, 2018 at 6:29 PM, Chris Angelico  wrote:
>>
>> On Mon, Mar 26, 2018 at 12:24 PM, Guido van Rossum 
>> wrote:
>> > The scope question is far from easy though. I find it particularly
>> > grating
>> > that an inline assignment occurs in an 'if' statement, its scope is the
>> > entire body of the 'if'. If that body is two pages long, by the end of
>> > it
>> > the reader (or even the writer!) may well have lost track of where it
>> > was
>> > defined and may be confused by the consequence past the end of the body.
>>
>> I think this one can be given to style guides. The useful situations
>> (eg regex match capturing) are sufficiently valuable that the
>> less-useful ones can just come along for the ride, just like "x =
>> lambda: ..." is perfectly valid even though "def" would be preferable.
>
>
> Not so fast. There's a perfectly reasonable alternative to sublocal scopes
> -- just let it assign to a local variable in the containing scope. That's
> the same as what Python does for for-loop variables. Note that for
> comprehensions it still happens to do the right thing (assuming we interpret
> the comprehension's private local scope to be the containing scope).
>
> This alternative has significant advantages in my view -- it doesn't require
> a whole new runtime mechanism to implement it (in CPython you can just
> generate bytecode to store and load locals), and it doesn't require a new
> section in the documentation to explain the new type of variable scope. Also
> it would make Steven happy. :-)

I'm still liking the sublocal system, but making assignment
expressions capable of standing plausibly without them is a Good
Thing.

> Perhaps we could even remove the requirement to parenthesize the new form of
> assignment, so we could say that at the statement level " = " and
> " := " just mean the same thing, or "=" is a shorthand for ":=",
> or whatever. In most cases it still makes sense to parenthesize it, since
> the := operator should have the same priority as the regular assignment
> operator, which means that "if x := f() and x != 42:" is broken and should
> be written as "if (x := f()) and x != 42:". But that could be a style guide
> issue. (Also note that I'm not proposing to add "+:=" etc., even though in C
> that's supported.)

That's about where I was thinking of putting it; "test" gets defined
potentially as "NAME := test". It has to right-associate.

At the moment, I'm going to be restricting it to simple names only, so
you can't say "x[1] := 2". That may be changed later.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Guido van Rossum
On Sun, Mar 25, 2018 at 6:29 PM, Chris Angelico  wrote:

> On Mon, Mar 26, 2018 at 12:24 PM, Guido van Rossum 
> wrote:
> > The scope question is far from easy though. I find it particularly
> grating
> > that an inline assignment occurs in an 'if' statement, its scope is the
> > entire body of the 'if'. If that body is two pages long, by the end of it
> > the reader (or even the writer!) may well have lost track of where it was
> > defined and may be confused by the consequence past the end of the body.
>
> I think this one can be given to style guides. The useful situations
> (eg regex match capturing) are sufficiently valuable that the
> less-useful ones can just come along for the ride, just like "x =
> lambda: ..." is perfectly valid even though "def" would be preferable.
>

Not so fast. There's a perfectly reasonable alternative to sublocal scopes
-- just let it assign to a local variable in the containing scope. That's
the same as what Python does for for-loop variables. Note that for
comprehensions it still happens to do the right thing (assuming we
interpret the comprehension's private local scope to be the containing
scope).

This alternative has significant advantages in my view -- it doesn't
require a whole new runtime mechanism to implement it (in CPython you can
just generate bytecode to store and load locals), and it doesn't require a
new section in the documentation to explain the new type of variable scope.
Also it would make Steven happy. :-)

Perhaps we could even remove the requirement to parenthesize the new form
of assignment, so we could say that at the statement level " = "
and " := " just mean the same thing, or "=" is a shorthand for
":=", or whatever. In most cases it still makes sense to parenthesize it,
since the := operator should have the same priority as the regular
assignment operator, which means that "if x := f() and x != 42:" is broken
and should be written as "if (x := f()) and x != 42:". But that could be a
style guide issue. (Also note that I'm not proposing to add "+:=" etc.,
even though in C that's supported.)

It would still require carefully defining execution order in all cases, but
we should probably do that anyway.

At some point we could introduce a "block" statement similar to Lua's
do/end or C's blocks (also found in many other languages). But there's not
really a lot of demand for this -- style guides justly frown upon functions
so long that they would benefit much.

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Tim Peters
[Tim]
>> I wonder whether Guido remembers this ;-)  In the very, very, VERY
>> early days, Python didn't have "==".  Plain single "=" was used for
>> both assignment and equality testing.

[Guido]
> Wow, I did not remember this. In fact I had to track down the 0.9.1 release
> that's somewhere on the web to see for myself. :-) Should add this to the
> HOPL-IV paper if I end up writing it (I'm still far from decided either
> way).

See?  I'm still good for _something_ sometimes ;-)


>> I'm not clear on why it changed.  I remember writing to Guido about
>> how to disambiguate between the "bind" and "test for equality" intents
>> in isolated expressions typed at the interactive prompt, and next
>> thing I knew the language changed to use "==" for the latter.

> Hm, that's probably why -- the desire for top-level expressions to allow
> comparison. Also probably the realization that this is one thing where (at
> the time) this particular difference with C/C++ was just annoying for most
> new users.

I don't have my email from those days, and have futilely tried to
recall details.  IIRC, it had never been discussed on the mailing list
before, or in any private emails before.  It just popped up one day
when I was working in a Python shell, and there was _something_ subtle
about it.  You wrote back and expressed disappointment - that you had
really wanted to keep "=" for both purposes.

I started writing a reply suggesting a way out of whatever-the-heck
the problem was, but before I finished the reply the next day you had
already changed the implementation!  Things moved quickly back then
:-)

Anyway, if your time machine is in good working order, I'd be pleased
if you went back and restored the original vision.  If, e.g., we
needed to type

>>> (x = y)
True

at the shell to get a top-level equality comparison, BFD.  I can't
believe it was _that_ simple, though.


> I'm assuming that <>, the ancient alternate spelling for != (that Barry
> still misses), came from the same source: ABC
> (https://homepages.cwi.nl/~steven/abc/qr.html#TESTS). But there was no
> compelling reason to remove <> (only to add !=) so it lingered until 3.0.
> Presumably ABC got both from Pascal
> (https://www.tutorialspoint.com/pascal/pascal_relational_operators.htm).

Good inspirations!  As noted next, at least Pascal used ":=" for assignment too.


> ...
> Most languages I learned in the '70s used it: both Algols, Pascal. (Though
> not Fortran.)

I mentioned Icon because I'm sure Pascal didn't have "embedded
assignments" at all.  Unsure about Algol, but I'd be surprised
(certainly not Fortran).

Icon has no "statements" at all:  _everything_ in Icon is an
expression, generating zero or more values.  Embedded assignments are
frequently used in idiomatic Icon, so I think it's especially relevant
that I recall no bugs due to Icon's use of ":=" (for assignment) and
"==" (for equality).  Programmers simply never used one when the other
was intended.  In C, essentially everyone uses "=" when they intend
"==" at times, and - as noted - I _still_ do that in Python regularly
to this day.  I'd be screwed if I got an unintended assignment instead
of a SyntaxError.


> ...
> The "two pages back" problem can happen just as easy with regular
> assignments or for-loop control variables.

Yup, but eyeballs don't have to scan every square inch of the screen
for those:  `for` statement targets are easy to find, and assignment
statement targets start flush with the first non-blank character of an
assignment statement, where the eye naturally leaps to.  When
assignments can be embedded anywhere, you have to look everywhere to
find them.

But so it goes.  Even if that can't be _stopped_, it's a matter of
good practice to avoid making code inscrutable.

> ...
> I gotta say I'm warming up to := in preference over 'as', *if* we're going
> to do this at all (not a foregone conclusion at all).

I'm not assuming it will go in, I just want to nudge the PEP toward a
proposal that doesn't suck so bad it's obviously doomed ;-)  I'm
uncertain whether I'd support it anyway.  I do know that, e.g.,

if m := match(string) is not None:
# do something with m

violates my sense of outrage less than anything else I've seen ;-)
And, ya, I'd _use_ it if it were implemented.  But I can (continue
to!) live without it.


> The scope question is far from easy though. I find it particularly grating
> that an inline assignment occurs in an 'if' statement, its scope is the
> entire body of the 'if'. If that body is two pages long, by the end of it
> the reader (or even the writer!) may well have lost track of where it was
> defined and may be confused by the consequence past the end of the body.

See my "every square inch" above ;-)  At least if the scope _is_
limited to the body of the `if`, it's far more limited than in C or
Icon.  Of course I'm more interested in whether it can be used to
write clearer code than in whether it can be abused to write muddier
code.

List 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Chris Angelico
On Mon, Mar 26, 2018 at 12:24 PM, Guido van Rossum  wrote:
> I gotta say I'm warming up to := in preference over 'as', *if* we're going
> to do this at all (not a foregone conclusion at all).

So am I, primarily due to its lack of syntactic ambiguities.

> The scope question is far from easy though. I find it particularly grating
> that an inline assignment occurs in an 'if' statement, its scope is the
> entire body of the 'if'. If that body is two pages long, by the end of it
> the reader (or even the writer!) may well have lost track of where it was
> defined and may be confused by the consequence past the end of the body.

I think this one can be given to style guides. The useful situations
(eg regex match capturing) are sufficiently valuable that the
less-useful ones can just come along for the ride, just like "x =
lambda: ..." is perfectly valid even though "def" would be preferable.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Guido van Rossum
On Sun, Mar 25, 2018 at 4:40 PM, Tim Peters  wrote:

> [Chris Angelico ]
> > ...
> > Not qualitative, but anecdotal: I do sometimes have to remind my
> > JavaScript students to check whether they've typed enough equals
> > signs. And that's in a language where the normal comparison operator
> > is ===. It's *still* not uncommon to see a comparison spelled =.
>
> I wonder whether Guido remembers this ;-)  In the very, very, VERY
> early days, Python didn't have "==".  Plain single "=" was used for
> both assignment and equality testing.  So way back then. using "=" for
> embedded assignment too was intractable on the face of it.
>

Wow, I did not remember this. In fact I had to track down the 0.9.1 release
that's somewhere on the web to see for myself. :-) Should add this to the
HOPL-IV paper if I end up writing it (I'm still far from decided either
way).


> I'm not clear on why it changed.  I remember writing to Guido about
> how to disambiguate between the "bind" and "test for equality" intents
> in isolated expressions typed at the interactive prompt, and next
> thing I knew the language changed to use "==" for the latter.
>

Hm, that's probably why -- the desire for top-level expressions to allow
comparison. Also probably the realization that this is one thing where (at
the time) this particular difference with C/C++ was just annoying for most
new users.

I'm assuming that <>, the ancient alternate spelling for != (that Barry
still misses), came from the same source: ABC (
https://homepages.cwi.nl/~steven/abc/qr.html#TESTS). But there was no
compelling reason to remove <> (only to add !=) so it lingered until 3.0.
Presumably ABC got both from Pascal (
https://www.tutorialspoint.com/pascal/pascal_relational_operators.htm).


> In any case, I really don't want to see plain "=" for embedded
> assignments now.
> [...]
>
> I'm fond enough of ":=".  Icon used that for assignment (embedded or
> otherwise), and I don't recall any bugs due to that.  It was hard to
> confuse for "==" (whether conceptual confusion or visual confusion).
>

Most languages I learned in the '70s used it: both Algols, Pascal. (Though
not Fortran.)


> That was just prone to the _other_ problem with embedded assignments:
> staring and staring trying to find the code where a name was most
> recently bound - "oh!  it was bound inside the third nested clause in
> the `while` test two pages back".  So it would be nice to combine
> embedded assignment with some notion of limited scope - but I'm much
> more concerned that the spelling not be easily confusable with "==".
> But not really a fan of overly wordy spellings either.
>

The "two pages back" problem can happen just as easy with regular
assignments or for-loop control variables.


> There you go!  All the rest follows trivially from the Zen of Python ;-)
>

I gotta say I'm warming up to := in preference over 'as', *if* we're going
to do this at all (not a foregone conclusion at all).

The scope question is far from easy though. I find it particularly grating
that an inline assignment occurs in an 'if' statement, its scope is the
entire body of the 'if'. If that body is two pages long, by the end of it
the reader (or even the writer!) may well have lost track of where it was
defined and may be confused by the consequence past the end of the body.

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Chris Angelico
On Mon, Mar 26, 2018 at 10:40 AM, Tim Peters  wrote:
> Here's one that baffled an office full of MIT grads for half a day
> before I noticed the real problem:
>
> assert(n=2);
>
> You can fill in the rest of the story yourself - but you'll miss the
> full extent of the agony it caused ;-)

I have to confess that my eye jumped down to the code before reading
all of the text above it, and as a result, I thought you were pointing
out that "n=2" for assignment would conflict with named argument
usage. Which it does, but that wasn't your point :)

Is there any way that ":=" can legally occur in Python source
(ignoring string literals) currently? A colon is always followed by a
'suite' or a 'test', neither of which can start with '=', and
annotated assignment has to have something between the ':' and '='. If
it's 100% unambiguous, it could be the solution to the current
wonkiness with 'as' having multiple meanings; in fact, there would
then be a new form of consistency: 'as' binds the special result of a
statement, but ':=' binds arbitrary expressions.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Tim Peters
[Chris Angelico ]
> ...
> Not qualitative, but anecdotal: I do sometimes have to remind my
> JavaScript students to check whether they've typed enough equals
> signs. And that's in a language where the normal comparison operator
> is ===. It's *still* not uncommon to see a comparison spelled =.

I wonder whether Guido remembers this ;-)  In the very, very, VERY
early days, Python didn't have "==".  Plain single "=" was used for
both assignment and equality testing.  So way back then. using "=" for
embedded assignment too was intractable on the face of it.

I'm not clear on why it changed.  I remember writing to Guido about
how to disambiguate between the "bind" and "test for equality" intents
in isolated expressions typed at the interactive prompt, and next
thing I knew the language changed to use "==" for the latter.

In any case, I really don't want to see plain "=" for embedded
assignments now.  It's been the source of some of the worst C
debugging nightmares I've wasted months of my life tracking down.
Here's one that baffled an office full of MIT grads for half a day
before I noticed the real problem:

assert(n=2);

You can fill in the rest of the story yourself - but you'll miss the
full extent of the agony it caused ;-)

Guido's original intuition was right:  regardless of programming
experience, it remains sorely tempting to write "x = y" when equality
testing is intended.  To this day I routinely get a syntax error in
Python when doing that by mistake.  For which I'm eternally grateful.

Any other way of spelling it would be preferable.  Requiring
parentheses around it isn't enough; e.g.,

if (x = 1) or (y = 2):

would almost certainly not do what was intended either.  There's also
that many newcomers from C-like languages habitually put all `if` and
`while` tests in parens.

I'm fond enough of ":=".  Icon used that for assignment (embedded or
otherwise), and I don't recall any bugs due to that.  It was hard to
confuse for "==" (whether conceptual confusion or visual confusion).

That was just prone to the _other_ problem with embedded assignments:
staring and staring trying to find the code where a name was most
recently bound - "oh!  it was bound inside the third nested clause in
the `while` test two pages back".  So it would be nice to combine
embedded assignment with some notion of limited scope - but I'm much
more concerned that the spelling not be easily confusable with "==".
But not really a fan of overly wordy spellings either.

There you go!  All the rest follows trivially from the Zen of Python ;-)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Guido van Rossum
On Sun, Mar 25, 2018 at 12:18 AM, Chris Angelico  wrote:

> [...]
> Agreed. I'm currently thinking that I need to do what several people
> have suggested and break this into two completely separate PEPs:
>
> 1) Sublocal namespacing
> 2) Assignment expressions
>
> Sublocal names can be used in a number of ways. There could be a "with
> sublocal EXPR as NAME:" syntax that actually disposes of the name
> binding at the end of the block, and "except Exception as e:" could
> shadow rather than unbinding. Maybe list comprehensions could change,
> too - instead of creating a function, they just create a sublocal
> scope.
>
> That may be the best way forward. I'm not sure.
>
> ChrisA
>

I don't think the PEP should be split up into two PEPs. The two topics are
too closely related. But you can have separate discussions about each issue
in the PEP.

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Zero Piraeus
:

On 25 March 2018 at 02:34, Guido van Rossum  wrote:
> [...] I think we need a short, crisp name for the new variable type.

Disposables?

 -[]z.
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Steven D'Aprano
On Sun, Mar 25, 2018 at 05:00:37PM +1000, Nick Coghlan wrote:

> Given the existing namespace stack of 
> builtin<-global<-nonlocal<-local, one potential short name would be 
> "sublocal", to indicate that these references are even more local than 
> locals (they're *so* local, they don't even appear in locals()!).

If we go down this track, +1 on the name "sublocal".


[...]
> And if we do end up going with the approach of defining a separate
> sublocal namespace, the fact that "n := ..." binds a sublocal, while
> "n = ..." and "... as n" both bind regular locals would be clearer
> than having the target scope of "as" be context dependent.

The scope issue is a good argument for avoiding "as" if we have sublocal 
binding.

One thing I like about the (expression as name) syntax is that the 
expression comes first. The Pascal-style := binding syntax reverses 
that. While we're bike-shedding, here are some alternatives to compare:


target = default if (expression as name) is None else name
target = default if (name := expression) is None else name
target = default if (expression -> name) is None else name
target = default if (name <- expression) is None else name


The arrow assignment operators <- and -> are both used by R. A dedicated 
non-ASCII forward arrow is also used by some programmable calculators, 
including HP and TI. But let's not start using non-ASCII symbols yet.

If we don't like a symbolic operator, we could channel BASIC from the 
1970s and write something like this:

target = default if (let expression = name) is None else name

Pros:

- requiring the keyword "let" prevents the "equals versus 
  assignment" class of errors;

- easier to search for a keyword than a symbolic operator;

Cons:

- more verbose;

- looks like BASIC;

- requires a new keyword.



-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Nick Coghlan
On 25 March 2018 at 17:18, Chris Angelico  wrote:
> Agreed. I'm currently thinking that I need to do what several people
> have suggested and break this into two completely separate PEPs:
>
> 1) Sublocal namespacing
> 2) Assignment expressions
>
> Sublocal names can be used in a number of ways. There could be a "with
> sublocal EXPR as NAME:" syntax that actually disposes of the name
> binding at the end of the block,

The scoping affects the name binding rather than the expression
evaluation, so I'd expect any such variant to be:

with EXPR as sublocal NAME:
...

> and "except Exception as e:" could
> shadow rather than unbinding. Maybe list comprehensions could change,
> too - instead of creating a function, they just create a sublocal
> scope.
>
> That may be the best way forward. I'm not sure.

I think you can treat it as an open design question within the current
PEP by tweaking the PEP title to be "Name binding as an expression".

If we allow expression level name binding at all, it will be an
either/or choice between binding to a new sublocal scope and binding
regular locals, and you can handle that by describing sublocals as
your current preferred option, but point out that the same *syntactic*
idea could be adopted without introducing the sublocals semantics (in
the latter case, the distinction created by the PEP would just be
between "assignment statements" and "assignment expressions", rather
than between "local assignments" and "sublocal assignments").

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Chris Angelico
On Sun, Mar 25, 2018 at 4:34 PM, Guido van Rossum  wrote:
> This is a super complex topic. There are at least three separate levels of
> critique possible, and all are important.

Thank you for your detailed post. I'll respond to some of it here, and
some more generally below.

> First there is the clarity of the PEP. Steven D'Aprano has given you great
> detailed feedback here and you should take it to heart (even if you disagree
> with his opinion about the specifics). I'd also recommend treating some of
> the "rejected alternatives" more like "open issues" (which are to be
> resolved during the review and feedback cycle). And you probably need some
> new terminology -- the abbreviation SNLB is awkward (I keep having to look
> it up), and I think we need a short, crisp name for the new variable type.

Agreed that it needs a new name. I've been trying to avoid looking for
something that's short-yet-inaccurate, and sticking to the
accurate-but-unwieldy; perhaps Nick's "sublocal" will serve the
purpose?

> Then there is the issue of syntax. While `(f() as x)` is a cool idea (and we
> should try to recover who deserves credit for first proposing it), it's easy
> to overlook in the middle of an exception. It's arguably more confusing
> because the scoping rules you propose are so different from the existing
> three other uses of `as NAME` -- and it causes an ugly wart in the PEP
> because two of those other uses are syntactically so close that you propose
> to ban SNLBs there. When it comes to alternatives, I think we've brainwashed
> ourselves into believing that inline assignments using `=` are evil that
> it's hard to objectively explain why it's bad -- we're just repeating the
> mantra here. I wish we could do more quantitative research into how bad this
> actually is in languages that do have it. We should also keep an open mind
> about alternative solutions present in other languages. Here it would be
> nice if we had some qualitative research into what other languages actually
> do (both about syntax and about semantics, for sure).

Not qualitative, but anecdotal: I do sometimes have to remind my
JavaScript students to check whether they've typed enough equals
signs. And that's in a language where the normal comparison operator
is ===. It's *still* not uncommon to see a comparison spelled =.

> The third issue is that of semantics. I actually see two issues here. One is
> whether we need a new scope (and whether it should be as weird as proposed).
> Steven seems to think we don't. I'm not sure that the counter-argument that
> we're already down that path with comprehension scopes is strong enough. The
> other issue is that, if we decide we *do* need (or want) statement-local
> scopes, the PEP must specify the exact scope of a name bound at any point in
> a statement. E.g. is `d[x] = (f() as x)` valid?

Yes, it is. The sublocal name (I'm going to give this term a try and
see how it works; if not, we can revert to "bullymong", err I mean
"SLNB") remains valid for all retrievals until the end of the
statement, which includes the assignment.

> And what should we do if a
> name may or may not be bound, as in `if (f(1) as x) or (f(2) as y): g(y)` --
> should that be a compile-time error (since we can easily tell that y isn't
> always defined when `g(y)` is called) or a runtime error (as we do for
> unbound "classic" locals)?

The way I've been thinking about it (and this is reflected in the
reference implementation) is that 'y' becomes, in effect, a new
variable that doesn't collide with any other 'y' in the same function
or module or anything. For the duration of this statement, 'x' and 'y'
are those special variables. So it's similar to writing this:

def func():
x = f(1)
if x:
g(y)
else:
y = f(2)
g(y)

which will raise UnboundLocalError when x is true. The same behaviour
happens here.

> And there are further details, e.g. are these
> really not allowed to be closures? And are they single-assignment? (Or can
> you do e.g. `(f(1) as x) + (f(2) as x)`?)

Technically, what happens is that the second one creates _another_
sublocal name, whose scope begins from the point of assignment and
goes to the end of the statement. Since this expression must all be
within one statement, both sublocals will expire simultaneously, so
it's effectively the same as reassigning to the same name, except that
the old object won't be dereferenced until the whole statement ends.
(And since Python-the-language doesn't guarantee anything about
dereferenced object destruction timings, this will just be a point of
curiosity.)

> So, there are lots of interesting questions! I do think there are somewhat
> compelling use cases; more than comprehensions (which I think are already
> over-used) I find myself frequently wishing for a better way to write
>
> m = pat.match(text)
> if m:
> g = m.group(0)
> if check(g):  # Some check that's not easily expressed as a regex
>

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-25 Thread Nick Coghlan
On 25 March 2018 at 15:34, Guido van Rossum  wrote:
>
> This is a super complex topic. There are at least three separate levels of 
> critique possible, and all are important.
>
> First there is the clarity of the PEP. Steven D'Aprano has given you great 
> detailed feedback here and you should take it to heart (even if you disagree 
> with his opinion about the specifics). I'd also recommend treating some of 
> the "rejected alternatives" more like "open issues" (which are to be resolved 
> during the review and feedback cycle). And you probably need some new 
> terminology -- the abbreviation SNLB is awkward (I keep having to look it 
> up), and I think we need a short, crisp name for the new variable type.

I've used "ephemeral name binding" before, but that's even longer than
saying ess-ell-enn-bee (for Statement Local Name Binding), and also
doesn't feel right for a proposal that allows the binding to persist
for the entire suite in compound statements.

Given the existing namespace stack of
builtin<-global<-nonlocal<-local, one potential short name would be
"sublocal", to indicate that these references are even more local than
locals (they're *so* local, they don't even appear in locals()!).

> Then there is the issue of syntax. While `(f() as x)` is a cool idea (and we 
> should try to recover who deserves credit for first proposing it),

I know I first suggested it years ago, but I don't recall if anyone
else proposed it before me.

> it's easy to overlook in the middle of an exception.

That I agree with - the more examples I've seen using it, the less
I've liked how visually similar "(a as b)" is to "(a and b)".

> It's arguably more confusing because the scoping rules you propose are so 
> different from the existing three other uses of `as NAME` -- and it causes an 
> ugly wart in the PEP because two of those other uses are syntactically so 
> close that you propose to ban SNLBs there. When it comes to alternatives, I 
> think we've brainwashed ourselves into believing that inline assignments 
> using `=` are evil that it's hard to objectively explain why it's bad -- 
> we're just repeating the mantra here. I wish we could do more quantitative 
> research into how bad this actually is in languages that do have it. We 
> should also keep an open mind about alternative solutions present in other 
> languages. Here it would be nice if we had some qualitative research into 
> what other languages actually do (both about syntax and about semantics, for 
> sure).

Writing "name = expr" when you meant "name == expr" remains a common
enough source of bugs in languages that allow it that I still wouldn't
want to bring that particular opportunity for semantically significant
typos over to Python.

Using "name := expr" doesn't have that problem though (since
accidentally adding ":" is a much harder typo to make than leaving out
"="), and has the added bonus that we could readily restrict the LHS
to single names. I also quite like the way it reads in conditional
expressions:

value = f() if (f := lookup_function(args)) is not None else default

And if we do end up going with the approach of defining a separate
sublocal namespace, the fact that "n := ..." binds a sublocal, while
"n = ..." and "... as n" both bind regular locals would be clearer
than having the target scope of "as" be context dependent.

> The third issue is that of semantics. I actually see two issues here. One is 
> whether we need a new scope (and whether it should be as weird as proposed). 
> Steven seems to think we don't. I'm not sure that the counter-argument that 
> we're already down that path with comprehension scopes is strong enough.

> The other issue is that, if we decide we *do* need (or want) statement-local 
> scopes, the PEP must specify the exact scope of a name bound at any point in 
> a statement. E.g. is `d[x] = (f() as x)` valid? And what should we do if a 
> name may or may not be bound, as in `if (f(1) as x) or (f(2) as y): g(y)` -- 
> should that be a compile-time error (since we can easily tell that y isn't 
> always defined when `g(y)` is called) or a runtime error (as we do for 
> unbound "classic" locals)? And there are further details, e.g. are these 
> really not allowed to be closures? And are they single-assignment? (Or can 
> you do e.g. `(f(1) as x) + (f(2) as x)`?)

I think this need to more explicitly specify evaluation order applies
regardless of whether we define a sublocal scope or not: expression
level name binding in any form makes evaluation order (and evaluation
scope!) matter in ways that we can currently gloss over, since you
need to be relying on functions with side effects in order to even
observe the differences.

If the expression level bindings are just ordinary locals, it does
open up some potentially interesting order of evaluation testing
techniques, though:

expected_order = list(range(3))
actual_order = iter(expected_order)
defaultdict(int)[(first := 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Guido van Rossum
This is a super complex topic. There are at least three separate levels of
critique possible, and all are important.

First there is the clarity of the PEP. Steven D'Aprano has given you great
detailed feedback here and you should take it to heart (even if you
disagree with his opinion about the specifics). I'd also recommend treating
some of the "rejected alternatives" more like "open issues" (which are to
be resolved during the review and feedback cycle). And you probably need
some new terminology -- the abbreviation SNLB is awkward (I keep having to
look it up), and I think we need a short, crisp name for the new variable
type.

Then there is the issue of syntax. While `(f() as x)` is a cool idea (and
we should try to recover who deserves credit for first proposing it), it's
easy to overlook in the middle of an exception. It's arguably more
confusing because the scoping rules you propose are so different from the
existing three other uses of `as NAME` -- and it causes an ugly wart in the
PEP because two of those other uses are syntactically so close that you
propose to ban SNLBs there. When it comes to alternatives, I think we've
brainwashed ourselves into believing that inline assignments using `=` are
evil that it's hard to objectively explain why it's bad -- we're just
repeating the mantra here. I wish we could do more quantitative research
into how bad this actually is in languages that do have it. We should also
keep an open mind about alternative solutions present in other languages.
Here it would be nice if we had some qualitative research into what other
languages actually do (both about syntax and about semantics, for sure).

The third issue is that of semantics. I actually see two issues here. One
is whether we need a new scope (and whether it should be as weird as
proposed). Steven seems to think we don't. I'm not sure that the
counter-argument that we're already down that path with comprehension
scopes is strong enough. The other issue is that, if we decide we *do* need
(or want) statement-local scopes, the PEP must specify the exact scope of a
name bound at any point in a statement. E.g. is `d[x] = (f() as x)` valid?
And what should we do if a name may or may not be bound, as in `if (f(1) as
x) or (f(2) as y): g(y)` -- should that be a compile-time error (since we
can easily tell that y isn't always defined when `g(y)` is called) or a
runtime error (as we do for unbound "classic" locals)? And there are
further details, e.g. are these really not allowed to be closures? And are
they single-assignment? (Or can you do e.g. `(f(1) as x) + (f(2) as x)`?)

I'm not sure if there are still places in Python where evaluation order is
unspecified, but I suspect there are (at the very least the reference
manual is incomplete in specifying the exact rules, e.g. I can't find words
specifying the evaluation order in a slice). We'll need to fix all of
those, otherwise the use of local name bindings in such cases would have
unspecified semantics (or the evaluation order could suddenly shift when a
local name binding was added).

So, there are lots of interesting questions! I do think there are somewhat
compelling use cases; more than comprehensions (which I think are already
over-used) I find myself frequently wishing for a better way to write

m = pat.match(text)
if m:
g = m.group(0)
if check(g):  # Some check that's not easily expressed as a regex
print(g)

It would be nice if I could write that as

if (m = pat.match(text)) and check((g = m.group(0))):
print(g)

or

if (pat.match(text) as m) and check((m.group(0) as g)):
print(g)

-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Ethan Furman

On 03/24/2018 09:27 AM, Rob Cliffe via Python-ideas wrote:

On 24/03/2018 14:44, Steven D'Aprano wrote:

On Sat, Mar 24, 2018 at 07:12:49PM +1000, Nick Coghlan wrote:



For PEP 572, the most directly comparable example is code like this:

 # Any previous binding of "m" is lost completely on the next line
 m = re.match(...)
 if m:
 print(m.groups(0))

In order to re-use that snippet, you need to double-check the surrounding
code and make sure that you're not overwriting an "m" variable already used
somewhere else in the current scope.

>>

Yes. So what? I'm going to be doing that regardless of whether the
interpreter places this use of m in its own scope or not. The scope as
seen by the interpreter is not important.

>

Good for you.  But the proposed scoping rules are an extra safeguard for 
programmers who are less conscientious than
you, or for anyone (including you) who is short of time, or misses something.  
An extra level of protection against
introducing a bug is IMO a Good Thing.


But it's not a free thing.  Our cars have seat belts, not six-point restraints, and either way the best practice is to 
be aware of one's surroundings, not rely on the safeguards to protect us against carelessness.




To the extent that this proposal to add sub-function scoping encourages
people to do copy-paste coding without even renaming variables to
something appropriate for the function they're pasted into, I think this
will strongly hurts readability in the long run.

>

I think it will aid readability, precisely for the reason Nick gives: you need 
to make fewer checks whether variables
are or are not used elsewhere.


Extra levels of intermingled scope are extra complication (for humans, too!); extra complication does not (usually) help 
readability -- I agree with D'Aprano that this is not a helpful complication.


--
~Ethan~
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Ethan Furman

On 03/24/2018 07:44 AM, Steven D'Aprano wrote:


I don't think we need sub-function scoping. I think it adds more
complexity that outweighs whatever benefit it gives.


+1

--
~Ethan~

___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Rob Cliffe via Python-ideas



On 24/03/2018 16:02, Steven D'Aprano wrote:


Yes, I get functions, and I think function-scope is a sweet spot between
too few scopes and too many. Remember the bad old days of BASIC when all
variables were application-global? Even if you used GOSUB as a
second-rate kind of function, all the variables were still global.

On the other hand, introducing sub-function scopes is, I strongly
believe, too many.



We are all entitled to our beliefs.
But the decision was made to stop a for-variable from leaking from a 
list comprehension - you may not agree with that decision, but it was 
presumably a reasonable one.
Using SLNBs that don't leak into the surrounding local scope is ISTM a 
similar decision, and one that, if made, would be made for similar reasons.

Rob Cliffe
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Rob Cliffe via Python-ideas



On 24/03/2018 14:44, Steven D'Aprano wrote:

On Sat, Mar 24, 2018 at 07:12:49PM +1000, Nick Coghlan wrote:
[...]


At a user experience level, the aim of the scoping limitation is
essentially to help improve "code snippet portability".

Consider the following piece of code:

 squares = [x**2 for x in iterable]

In Python 2.x, you not only have to check whether or not you're already
using "squares" for something, you also need to check whether or not you're
using "x", since the iteration variable leaks.

[...]


For PEP 572, the most directly comparable example is code like this:

 # Any previous binding of "m" is lost completely on the next line
 m = re.match(...)
 if m:
 print(m.groups(0))

In order to re-use that snippet, you need to double-check the surrounding
code and make sure that you're not overwriting an "m" variable already used
somewhere else in the current scope.

Yes. So what? I'm going to be doing that regardless of whether the
interpreter places this use of m in its own scope or not. The scope as
seen by the interpreter is not important.
Good for you.  But the proposed scoping rules are an extra safeguard for 
programmers who are less conscientious than you, or for anyone 
(including you) who is short of time, or misses something.  An extra 
level of protection against introducing a bug is IMO a Good Thing.

  If all we cared about was
avoiding name collisions, we could solve that by using 128-bit secret
keys as variables:

 var_81c199e61e9f90fd023508aee3265ad9

Good luck with that. :-)


We don't need multiple scopes to avoid name collisions, we just need to
make sure they're all unique :-)
You could use the same argument to justify "We don't need separate local 
and global scopes".  But we have them, and it makes it easier and safer 
to cut-and-paste functions.  I assume you don't consider that a Bad Thing.


But of course readability counts, and we write code to be read by
people, not for the convenience of the interpreter.

For that reason, whenever I paste a code snippet, I'm going to check the
name and make a conscious decision whether to keep it or change it, and
doing that means I have to check whether "m" is already in use
regardless of whether or not the interpreter will keep the two (or
more!) "m" variables. So this supposed benefit is really no benefit at
all. I still am going to check "m" to see if it clashes.
Same argument, same reply.  Good for you - but there's nothing wrong 
with an extra safety net.
And you make essentially the same point a few more times, I won't repeat 
myself further.


To the extent that this proposal to add sub-function scoping encourages
people to do copy-paste coding without even renaming variables to
something appropriate for the function they're pasted into, I think this
will strongly hurts readability in the long run.
I think it will aid readability, precisely for the reason Nick gives: 
you need to make fewer checks whether variables are or are not used 
elsewhere.




With PEP 572, you don't even need to look, since visibility of the "m" in
the following snippet is automatically limited to the statement itself:

 if (re.match(...) as m):
 print(m.groups(0))
 # Any previous binding of "m" is visible again here, and hence a common
source of bugs is avoided :)

Is this really a "common source of bugs"?

Do you really mean to suggest that we should be able to copy and paste a
code snippet into the middle of a function without checking how it
integrates with the surrounding code? Because that's what it seems that
you are saying. And not only that we should be able to do so, but that
it is important enough that we should add a feature to encourage it?

If people make a habit of pasting snippets of code into their functions
without giving any thought to how it fits in with the rest of the
function, then any resulting bugs are caused by carelessness and
slap-dash technique, not the scoping rules of the language.

The last thing I want to read is a function where the same name is used
for two or three or a dozen different things, because the author
happened to copy code snippets from elsewhere and didn't bother renaming
things to be more appropriate. Nevermind whether the interpreter can
keep track of which is which, I'm worried about *my* ability to keep
track of which is which.

I might be cynical about the professionalism and skills of the average
programmer, but even I find it hard to believe that most people would
actually do that. But since we're (surely?) going to be taking time to
integrate the snippet with the rest of the function, the benefit of not
having to check for duplicate variable names evaporates.

We (hopefully!) will be checking for duplicates regardless of whether
they are scoped to a single statement or not, because we don't want to
read and maintain a function with the same name "x" representing a dozen
different things at different times.

I'm not opposed to re-using variable names 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Steven D'Aprano
On Sat, Mar 24, 2018 at 08:49:08PM +1100, Chris Angelico wrote:

> > [(spam, spam+1) for x in values for spam in (func(x),)]
> >
> > [(spam, spam+1) for spam in (func(x) for x in values)]
> >
> > They are the equivalent to "just add another assignment statement" for
> > comprehensions.
> 
> They might be mechanically equivalent. They are not syntactically
> equivalent. This PEP is not about "hey let's do something in Python
> that's utterly impossible to do". It's "here's a much tidier way to
> spell something that currently has to be ugly".

For the record, I don't think either of those are ugly. The first is a 
neat trick, but the second in particular is a natural, elegant and 
beautiful way of doing it in a functional style. And the beauty of it 
is, if it ever becomes too big and unwieldy for a single expression, it 
is easy to *literally* "just add another assignment statement":

eggs = (long_and_complex_expression_with(x) for x in values)
[(spam, spam+1) for spam in eggs)]

So I stand by my claim that even for comprehensions, "just add another 
assignment statement" is always an alternative.


> > while ("spam" as x):
> > assert x == "spam"
> > while ("eggs" as x):
> > assert x == "eggs"
> > break
> > assert x == "eggs"
> 
> That means that sometimes, ``while ("eggs" as x):`` creates a new
> variable, and sometimes it doesn't. Why should that be?

I'm not following you.

If we talk implementation for a moment, my proposal is that x is just a 
regular local variable. So the CPython compiler sees (... as x) in the 
code and makes a slot for it in the function. (Other implementations may 
do differently.) Whether or not that local slot gets filled with a value 
depends on whether or not the specific (... as x) actually gets executed 
or not. That's no different from any other binding operation.

If x is defined as global, then (... as x) will bind to the global, not 
the local, but otherwise will behave the same.


[...]
> Function-local names give the same confidence. It doesn't matter what
> names you use inside a function (modulo 'global' or 'nonlocal'
> declarations) - they quietly shadow anything from the outside.

Yes, I get functions, and I think function-scope is a sweet spot between 
too few scopes and too many. Remember the bad old days of BASIC when all 
variables were application-global? Even if you used GOSUB as a 
second-rate kind of function, all the variables were still global.

On the other hand, introducing sub-function scopes is, I strongly 
believe, too many.


[...]
> > I think the rule should be either:
> >
> > - statement-locals actually *are* locals and so behave like locals;
> >
> > - statement-locals introduce a new scope, but still behave like
> >   locals with respect to closures.
> >
> > No need to introduce two separate modes of behaviour. (Or if there is
> > such a need, then the PEP should explain it.)
> 
> That would basically mean locking in some form of semantics.
> For your first example, you're locking in the rule that "(g(i) as x)" 
> is exactly the same as "x = g(i)", and you HAVE to then allow that 
> this will potentially assign to global or nonlocal names as well 
> (subject to the usual rules). In other words, you have 
> assignment-as-expression without any form of subscoping. This is a 
> plausible stance and may soon be becoming a separate PEP.

Well, if we really wanted to, we could ban (expression as name) where 
name was declared global, but why bother?


> But for your second, you're locking in the same oddities that a 'with'
> block has: that a variable is being "created" and "destroyed", yet it
> sticks around for the rest of the function, just in case.

Where is it documented that with blocks destroy variables? They don't. 
`with expression as name` is a name-binding operation no different from 
`name = expression` and the others. With the sole special case of except 
blocks auto-magically deleting the exception name, the only way to 
unbind a name is to call `del`.

What you're describing is not an oddity, but the standard way variables 
work in Python, and damn useful too. I have code that requires that the 
`with` variable is not unbound at the end of the block.


> It's a source of some confusion to people that the name used in a 
> 'with' statement is actually still valid afterwards.

The difference between "import foo" and "from foo import bar" is source 
of some confusion to some people. I should know, because I went through 
that period myself.

Just because "some people" make unjustified assumptions about the 
semantics of a language feature doesn't necessarily mean the language 
feature is wrong or harmful.


> Or does it only stick
> around if there is a function to close over it?

No, there's no need for a closure:

py> with open("/tmp/foo", "w") as f:
... pass
...
py> f.closed
True
py> f.name
'/tmp/foo'

 
> Honestly, I really want to toss this one into the "well don't do that"
> 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Steven D'Aprano
On Sat, Mar 24, 2018 at 07:12:49PM +1000, Nick Coghlan wrote:

> > I think that needs justification by more than just "it makes the
> > implementation easier".
>
> Introducing the new scoping behaviour doesn't make the implementation
> easier, it makes it harder.
[...]

Perhaps I had misunderstood something Chris had said.


> At a user experience level, the aim of the scoping limitation is
> essentially to help improve "code snippet portability".
> 
> Consider the following piece of code:
> 
> squares = [x**2 for x in iterable]
> 
> In Python 2.x, you not only have to check whether or not you're already
> using "squares" for something, you also need to check whether or not you're
> using "x", since the iteration variable leaks.

I hear you, and I understand that some people had problems with leakage, 
but in my own experience, this was not a problem I ever had. On the 
contrary, it was occasionally useful (what was the last value x took 
before the comprehension finished?).

The change to Python 3 non-leaking behaviour has solved no problem for 
me but taken away something which was nearly always harmless and very 
occasionally useful. So I don't find this to be an especially compelling 
argument.

But at least comprehensions are intended to be almost entirely 
self-contained, so it's not actively harmful. But I can't say the same 
for additional sub-function scopes.


> For PEP 572, the most directly comparable example is code like this:
> 
> # Any previous binding of "m" is lost completely on the next line
> m = re.match(...)
> if m:
> print(m.groups(0))
> 
> In order to re-use that snippet, you need to double-check the surrounding
> code and make sure that you're not overwriting an "m" variable already used
> somewhere else in the current scope.

Yes. So what? I'm going to be doing that regardless of whether the 
interpreter places this use of m in its own scope or not. The scope as 
seen by the interpreter is not important. If all we cared about was 
avoiding name collisions, we could solve that by using 128-bit secret 
keys as variables:

var_81c199e61e9f90fd023508aee3265ad9

We don't need multiple scopes to avoid name collisions, we just need to 
make sure they're all unique :-)

But of course readability counts, and we write code to be read by 
people, not for the convenience of the interpreter.

For that reason, whenever I paste a code snippet, I'm going to check the 
name and make a conscious decision whether to keep it or change it, and 
doing that means I have to check whether "m" is already in use 
regardless of whether or not the interpreter will keep the two (or 
more!) "m" variables. So this supposed benefit is really no benefit at 
all. I still am going to check "m" to see if it clashes.

To the extent that this proposal to add sub-function scoping encourages 
people to do copy-paste coding without even renaming variables to 
something appropriate for the function they're pasted into, I think this 
will strongly hurts readability in the long run.


> With PEP 572, you don't even need to look, since visibility of the "m" in
> the following snippet is automatically limited to the statement itself:
> 
> if (re.match(...) as m):
> print(m.groups(0))
> # Any previous binding of "m" is visible again here, and hence a common
> source of bugs is avoided :)

Is this really a "common source of bugs"?

Do you really mean to suggest that we should be able to copy and paste a 
code snippet into the middle of a function without checking how it 
integrates with the surrounding code? Because that's what it seems that 
you are saying. And not only that we should be able to do so, but that 
it is important enough that we should add a feature to encourage it?

If people make a habit of pasting snippets of code into their functions 
without giving any thought to how it fits in with the rest of the 
function, then any resulting bugs are caused by carelessness and 
slap-dash technique, not the scoping rules of the language.

The last thing I want to read is a function where the same name is used 
for two or three or a dozen different things, because the author 
happened to copy code snippets from elsewhere and didn't bother renaming 
things to be more appropriate. Nevermind whether the interpreter can 
keep track of which is which, I'm worried about *my* ability to keep 
track of which is which.

I might be cynical about the professionalism and skills of the average 
programmer, but even I find it hard to believe that most people would 
actually do that. But since we're (surely?) going to be taking time to 
integrate the snippet with the rest of the function, the benefit of not 
having to check for duplicate variable names evaporates.

We (hopefully!) will be checking for duplicates regardless of whether 
they are scoped to a single statement or not, because we don't want to 
read and maintain a function with the same name "x" representing a dozen 
different things 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Eric V. Smith

On 3/24/2018 5:49 AM, Chris Angelico wrote:

On Sat, Mar 24, 2018 at 3:41 PM, Steven D'Aprano  wrote:

To keep this a manageable length, I've trimmed vigourously. Apologies in
advance if I've been too enthusiastic with the trimming :-)
On Sat, Mar 24, 2018 at 05:09:54AM +1100, Chris Angelico wrote:


...


"SLNB"? Undefined acronym. What is it? I presume it has something to do
with the single-statement variable.


Statement-Local Name Binding, from the title of the PEP. (But people
probably don't read titles.)


Indeed. In case it isn't obvious, you should define the acronym the
first time you use it in the PEP.


Once again, I assumed too much of people. Expected them to actually
read the stuff they're discussing. And once again, the universe
reminds me that people aren't like that. Ah well. Will fix that next
round of edits.


To be fair to the readers, you don't indicate you're going to use part 
of the title as an acronym later. I certainly didn't get it, either, and 
I read the title and PEP. So I'm in the group of people you assumed too 
much of.


The traditional way to specify this would be to change part of the title 
or first usage to:


"Statement-Local Name Binding (SLNB)".

Which is a signal to the reader that you're going to use this later and 
they should remember it.


I don't know if it's frowned upon, but I wouldn't put this in the title. 
Instead, I'd put it in the body of the PEP on first usage. And I'd also 
make that usage in the first paragraph, instead of many paragraphs in.


Eric
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Paul Moore
On 24 March 2018 at 09:49, Chris Angelico  wrote:
>> Of course we don't want to necessarily impose unreasonable performance
>> and maintence costs on any implementation. But surely performance
>> cost is a quality of implementation issue. It ought to be a matter of
>> trade-offs: is the benefit sufficient to make up for the cost?
>
> I don't see where this comes in. Let's say that Jython can't implement
> this feature without a 10% slowdown in run-time performance even if
> these subscopes aren't used. What are you saying the PEP should say?
> That it's okay for this feature to hurt performance by 10%? Then it
> should be rightly rejected. Or that Jython is allowed to ignore this
> feature? Or what?

I think the PEP should confirm that there's not expected to be a
showstopper performance cost in implementing this feature in other
Python implementations. That doesn't have to be a big deal - reaching
out to the Jython, PyPy, Cython etc implementors and asking them for a
quick sanity check that this doesn't impose unmanageable overheads
should be sufficient. No need to make this too dogmatic.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Chris Angelico
On Sat, Mar 24, 2018 at 3:41 PM, Steven D'Aprano  wrote:
> To keep this a manageable length, I've trimmed vigourously. Apologies in
> advance if I've been too enthusiastic with the trimming :-)
>
> On Sat, Mar 24, 2018 at 05:09:54AM +1100, Chris Angelico wrote:
>
>> No, I haven't yet. Sounds like a new section is needed. Thing is,
>> there's a HUGE family of C-like and C-inspired languages that allow
>> assignment expressions, and for the rest, I don't have any personal
>> experience. So I need input from people: what languages do you know of
>> that have small-scope name bindings like this?
>
> I don't know if this counts as "like this", but Lua has a do...end block
> that introduces a new scope. Something like this:
>
> x = 1
> do:
>x = 2
>print(x)  # prints 2
> end
> print(x)  # prints 1
>
> I think that's a neat concept, but I'm struggling to think what I would
> use it for.

Okay. I'll leave off for now, but if the split of PEPs happens, I'll
need to revisit that.

>> > result = (func(x), func(x)+1, func(x)*2)
>>
>> True, but outside of comprehensions, the most obvious response is
>> "just add another assignment statement". You can't do that in a list
>> comp (or equivalently in a genexp or dict comp).
>
> Yes you can: your PEP gives equivalents that work fine for list comps,
> starting with factorising the duplicate code out into a helper function,
> to using a separate loop to get assignment:
>
> [(spam, spam+1) for x in values for spam in (func(x),)]
>
> [(spam, spam+1) for spam in (func(x) for x in values)]
>
> They are the equivalent to "just add another assignment statement" for
> comprehensions.

They might be mechanically equivalent. They are not syntactically
equivalent. This PEP is not about "hey let's do something in Python
that's utterly impossible to do". It's "here's a much tidier way to
spell something that currently has to be ugly".

> Strictly speaking, there's never a time that we cannot use a new
> assignment statement. But sometimes it is annoying or inconvenient.
> Consider a contrived example:
>
> TABLE = [
>  alpha,
>  beta,
>  gamma,
>  delta,
>  ...
>  func(omega) + func(omega)**2 + func(omega)**3,
>  ]
>
>
> Yes, I can pull out the duplication:
>
> temp = function(omega)
> TABLE = [
>  alpha,
>  beta,
>  gamma,
>  delta,
>  ...
>  temp + temp**2 + temp**3,
>  ]
>
> but that puts the definition of temp quite distant from its use. So this
> is arguably nicer:
>
> TABLE = [
>  alpha,
>  beta,
>  gamma,
>  delta,
>  ...
>  (func(omega) as temp) + temp**2 + temp**3,
>  ]

Right. Definitely advantageous (and another reason not to go with the
comprehension-specific options).

>> >> Just as function-local names shadow global names for the scope of the
>> >> function, statement-local names shadow other names for that statement.
>> >> (They can technically also shadow each other, though actually doing this
>> >> should not be encouraged.)
>> >
>> > That seems weird.
>>
>> Which part? That they shadow, or that they can shadow each other?
>
> Shadowing themselves.
>
> I'm still not convinced these should just shadow local variables. Of
> course locals will shadow nonlocals, which shadow globals, which shadow
> builtins. I'm just not sure that we gain much (enough?) to justify
> adding a new scope between what we already have:
>
> proposed statement-local
> local
> nonlocal
> class (only during class statement)
> global
> builtins
>
> I think that needs justification by more than just "it makes the
> implementation easier".

Nick has answered this part better than I can, so I'll just say "yep,
read his post". :)

>> Shadowing is the same as nested functions (including comprehensions,
>> since they're implemented with functions); and if SLNBs are *not* to
>> shadow each other, the only way is to straight-up disallow it.
>
> Or they can just rebind to the same (statement-)local. E.g.:
>
> while ("spam" as x):
> assert x == "spam"
> while ("eggs" as x):
> assert x == "eggs"
> break
> assert x == "eggs"

That means that sometimes, ``while ("eggs" as x):`` creates a new
variable, and sometimes it doesn't. Why should that be?

If you change the way that "spam" is assigned to x, the semantics of
the inner 'while' block shouldn't change. It creates a subscope, it
uses that subscope, the subscope expires. Curtain comes down. By your
proposal, you have to check whether 'x' is shadowing some other
variable, and if so, what type. By mine, it doesn't matter; regardless
of whether 'x' existed or not, regardless of whether there's any other
x in any other scope, that loop behaves the same way.

Function-local names give the same confidence. It doesn't matter what
names you use inside a function (modulo 'global' or 'nonlocal'
declarations) - they 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Nick Coghlan
On 24 March 2018 at 14:41, Steven D'Aprano  wrote:

> On Sat, Mar 24, 2018 at 05:09:54AM +1100, Chris Angelico wrote:
> > >> Just as function-local names shadow global names for the scope of the
> > >> function, statement-local names shadow other names for that statement.
> > >> (They can technically also shadow each other, though actually doing
> this
> > >> should not be encouraged.)
> > >
> > > That seems weird.
> >
> > Which part? That they shadow, or that they can shadow each other?
>
> Shadowing themselves.
>
> I'm still not convinced these should just shadow local variables. Of
> course locals will shadow nonlocals, which shadow globals, which shadow
> builtins. I'm just not sure that we gain much (enough?) to justify
> adding a new scope between what we already have:
>
> proposed statement-local
> local
> nonlocal
> class (only during class statement)
> global
> builtins
>
> I think that needs justification by more than just "it makes the
> implementation easier".
>

Introducing the new scoping behaviour doesn't make the implementation
easier, it makes it harder. However, there are specific aspects of how that
proposed new scope works (like not being visible from nested scopes) that
make the implementation easier, since they eliminate a whole swathe of
otherwise complicated semantic questions :)

At a user experience level, the aim of the scoping limitation is
essentially to help improve "code snippet portability".

Consider the following piece of code:

squares = [x**2 for x in iterable]

In Python 2.x, you not only have to check whether or not you're already
using "squares" for something, you also need to check whether or not you're
using "x", since the iteration variable leaks.

In Python 3.x, you only need to check for "squares" usage, since the
comprehension has its own inner scope, and any "x" binding you may have
defined will be shadowed instead of being overwritten.

For PEP 572, the most directly comparable example is code like this:

# Any previous binding of "m" is lost completely on the next line
m = re.match(...)
if m:
print(m.groups(0))

In order to re-use that snippet, you need to double-check the surrounding
code and make sure that you're not overwriting an "m" variable already used
somewhere else in the current scope.

With PEP 572, you don't even need to look, since visibility of the "m" in
the following snippet is automatically limited to the statement itself:

if (re.match(...) as m):
print(m.groups(0))
# Any previous binding of "m" is visible again here, and hence a common
source of bugs is avoided :)

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-24 Thread Nick Coghlan
On 24 March 2018 at 04:09, Chris Angelico  wrote:

> On Sat, Mar 24, 2018 at 2:00 AM, Steven D'Aprano 
> wrote:
> > I see you haven't mentioned anything about Nick Coglan's (long ago)
> > concept of a "where" block. If memory serves, it would be something
> > like:
> >
> > value = x**2 + 2*x where:
> > x = some expression
> >
> > These are not necessarily competing, but they are relevant.
>
> Definitely relevant, thanks. This is exactly what I'm looking for -
> related proposals that got lost in the lengthy threads on the subject.
> I'll mention it as another proposal, but if anyone has an actual post
> for me to reference, that would be appreciated (just to make sure I'm
> correctly representing it).
>

That one's a PEP reference: https://www.python.org/dev/peps/pep-3150/

If PEP 572 were to happen, then I'd see some variant of PEP 3150 as a
potential future follow-on (allowing the statement local namespace for a
simple statement to be populated in a trailing suite, without needing to
make the case for statement locals in the first place).

If inline local variable assignment were to happen instead, then PEP 3150
would continue to face the double hurdle of pitching both the semantic
benefits of statement locals, while also pitching a syntax for defining
them.

FWIW, I like this version of the statement local proposal, and think it
would avoid a lot of the quirks that otherwise arise when allowing
expression level assignments.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Steven D'Aprano
To keep this a manageable length, I've trimmed vigourously. Apologies in 
advance if I've been too enthusiastic with the trimming :-)

On Sat, Mar 24, 2018 at 05:09:54AM +1100, Chris Angelico wrote:

> No, I haven't yet. Sounds like a new section is needed. Thing is,
> there's a HUGE family of C-like and C-inspired languages that allow
> assignment expressions, and for the rest, I don't have any personal
> experience. So I need input from people: what languages do you know of
> that have small-scope name bindings like this?

I don't know if this counts as "like this", but Lua has a do...end block 
that introduces a new scope. Something like this:

x = 1
do:
   x = 2
   print(x)  # prints 2
end
print(x)  # prints 1

I think that's a neat concept, but I'm struggling to think what I would 
use it for.


[...]
> > result = (func(x), func(x)+1, func(x)*2)
> 
> True, but outside of comprehensions, the most obvious response is
> "just add another assignment statement". You can't do that in a list
> comp (or equivalently in a genexp or dict comp).

Yes you can: your PEP gives equivalents that work fine for list comps, 
starting with factorising the duplicate code out into a helper function, 
to using a separate loop to get assignment:

[(spam, spam+1) for x in values for spam in (func(x),)]

[(spam, spam+1) for spam in (func(x) for x in values)]

They are the equivalent to "just add another assignment statement" for 
comprehensions.

I acknowledge that comprehensions are the motivating example here, but I 
don't think they're the only justification for the concept.

Strictly speaking, there's never a time that we cannot use a new 
assignment statement. But sometimes it is annoying or inconvenient. 
Consider a contrived example:

TABLE = [
 alpha,
 beta,
 gamma,
 delta,
 ...
 func(omega) + func(omega)**2 + func(omega)**3,
 ]


Yes, I can pull out the duplication:

temp = function(omega)
TABLE = [
 alpha,
 beta,
 gamma,
 delta,
 ...
 temp + temp**2 + temp**3,
 ]

but that puts the definition of temp quite distant from its use. So this 
is arguably nicer:

TABLE = [
 alpha,
 beta,
 gamma,
 delta,
 ...
 (func(omega) as temp) + temp**2 + temp**3,
 ]



> >> Just as function-local names shadow global names for the scope of the
> >> function, statement-local names shadow other names for that statement.
> >> (They can technically also shadow each other, though actually doing this
> >> should not be encouraged.)
> >
> > That seems weird.
> 
> Which part? That they shadow, or that they can shadow each other?

Shadowing themselves.

I'm still not convinced these should just shadow local variables. Of 
course locals will shadow nonlocals, which shadow globals, which shadow 
builtins. I'm just not sure that we gain much (enough?) to justify 
adding a new scope between what we already have:

proposed statement-local
local
nonlocal
class (only during class statement)
global
builtins

I think that needs justification by more than just "it makes the 
implementation easier".


> Shadowing is the same as nested functions (including comprehensions,
> since they're implemented with functions); and if SLNBs are *not* to
> shadow each other, the only way is to straight-up disallow it.

Or they can just rebind to the same (statement-)local. E.g.:

while ("spam" as x):
assert x == "spam"
while ("eggs" as x):
assert x == "eggs"
break
assert x == "eggs"



> > Why can they not be used in closures? I expect that's going to cause a
> > lot of frustration.
> 
> Conceptually, the variable stops existing at the end of that
> statement. It makes for some oddities, but fewer oddities than every
> other variant that I toyed with. For example, does this create one
> single temporary or many different temporaries?
> 
> def f():
> x = "outer"
> funcs = {}
> for i in range(10):
> if (g(i) as x) > 0:
> def closure():
> return x
> funcs[x] = closure

I think the rule should be either:

- statement-locals actually *are* locals and so behave like locals;

- statement-locals introduce a new scope, but still behave like 
  locals with respect to closures.

No need to introduce two separate modes of behaviour. (Or if there is 
such a need, then the PEP should explain it.)


> > I think there's going to be a lot of confusion about which uses of "as"
> > bind to a new local and which don't.
> 
> That's the exact point of "statement-local" though.

I don't think so. As I say:

> > I think this proposal is conflating two unrelated concepts:
> >
> > - introducing new variables in order to meet DRY requirements;
> >
> > - introducing a new scope.

If you're going to champion *both* concepts, then you need to justify 
them both in the PEP, not just assume its obvious why we want 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Chris Angelico
On Sat, Mar 24, 2018 at 2:00 AM, Steven D'Aprano  wrote:
> On Fri, Mar 23, 2018 at 09:01:01PM +1100, Chris Angelico wrote:
>
>> PEP: 572
>> Title: Syntax for Statement-Local Name Bindings
> [...]
>> Abstract
>> 
>>
>> Programming is all about reusing code rather than duplicating it.
>
> I don't think that editorial comment belongs here, or at least, it is
> way too strong. I'm pretty sure that programming is not ALL about reusing 
> code,
> and code duplication is not always wrong.
>
> Rather, we can say that *often* we want to avoid code duplication, and
> this proposal is way way to do so. And this should go into the
> Rationale, not the Abstract. The abstract should describe what this
> proposal *does*, not why, for example:
>
> This is a proposal for permitting temporary name bindings
> which are limited to a single statement.
>
> What the proposal *is* goes in the Abstract; reasons *why* we want it
> go in the Rationale.

Thanks. I've never really been happy with my "Abstract" / "Rationale"
split, as they're two sections both designed to give that initial
'sell', and I'm clearly not good at writing the distinction :)

Unless you object, I'm just going to steal your Abstract wholesale.
Seems like some good words there.

> I see you haven't mentioned anything about Nick Coglan's (long ago)
> concept of a "where" block. If memory serves, it would be something
> like:
>
> value = x**2 + 2*x where:
> x = some expression
>
> These are not necessarily competing, but they are relevant.

Definitely relevant, thanks. This is exactly what I'm looking for -
related proposals that got lost in the lengthy threads on the subject.
I'll mention it as another proposal, but if anyone has an actual post
for me to reference, that would be appreciated (just to make sure I'm
correctly representing it).

> Nor have you done a review of any other languages, to see what similar
> features they already offer. Not even the C's form of "assignment as an
> expression" -- you should refer to that, and explain why this would not
> similarly be a bug magnet.

No, I haven't yet. Sounds like a new section is needed. Thing is,
there's a HUGE family of C-like and C-inspired languages that allow
assignment expressions, and for the rest, I don't have any personal
experience. So I need input from people: what languages do you know of
that have small-scope name bindings like this?

>> Rationale
>> =
>>
>> When a subexpression is used multiple times in a list comprehension,
>
> I think that list comps are merely a single concrete example of a more
> general concept that we sometimes want or need to apply the DRY
> principle to a single expression.
>
> This is (usually) a violation of DRY whether it is inside or outside of
> a list comp:
>
> result = (func(x), func(x)+1, func(x)*2)

True, but outside of comprehensions, the most obvious response is
"just add another assignment statement". You can't do that in a list
comp (or equivalently in a genexp or dict comp). Syntactically you're
right that they're just one example of a general concept; but they're
one of the original motivating reasons. I've tweaked the rationale
wording some; the idea is now "here's a general idea" followed by two
paragraphs of specific use-cases (comprehensions and loops). Let me
know if that works better.

>> Syntax and semantics
>> 
>>
>> In any context where arbitrary Python expressions can be used, a **named
>> expression** can appear. This must be parenthesized for clarity, and is of
>> the form ``(expr as NAME)`` where ``expr`` is any valid Python expression,
>> and ``NAME`` is a simple name.
>>
>> The value of such a named expression is the same as the incorporated
>> expression, with the additional side-effect that NAME is bound to that
>> value for the remainder of the current statement.
>
>
> Examples should go with the description. Such as:
>
> x = None if (spam().ham as eggs) is None else eggs

Not sure what you gain out of that :) Maybe a different first
expression would help.

> y = ((spam() as eggs), (eggs.method() as cheese), cheese[eggs])

Sure. I may need to get some simpler examples to kick things off though.

>> Just as function-local names shadow global names for the scope of the
>> function, statement-local names shadow other names for that statement.
>> (They can technically also shadow each other, though actually doing this
>> should not be encouraged.)
>
> That seems weird.

Which part? That they shadow, or that they can shadow each other?
Shadowing is the same as nested functions (including comprehensions,
since they're implemented with functions); and if SLNBs are *not* to
shadow each other, the only way is to straight-up disallow it. For the
moment, I'm not forbidding it, as there's no particular advantage to
popping a SyntaxError.

>> Assignment to statement-local names is ONLY through this syntax. Regular
>> assignment to the same name will remove the 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Steven D'Aprano
On Fri, Mar 23, 2018 at 09:01:01PM +1100, Chris Angelico wrote:

> PEP: 572
> Title: Syntax for Statement-Local Name Bindings
[...]
> Abstract
> 
> 
> Programming is all about reusing code rather than duplicating it.

I don't think that editorial comment belongs here, or at least, it is 
way too strong. I'm pretty sure that programming is not ALL about reusing code, 
and code duplication is not always wrong.

Rather, we can say that *often* we want to avoid code duplication, and 
this proposal is way way to do so. And this should go into the 
Rationale, not the Abstract. The abstract should describe what this 
proposal *does*, not why, for example:

This is a proposal for permitting temporary name bindings 
which are limited to a single statement.

What the proposal *is* goes in the Abstract; reasons *why* we want it 
go in the Rationale.

I see you haven't mentioned anything about Nick Coglan's (long ago) 
concept of a "where" block. If memory serves, it would be something 
like:

value = x**2 + 2*x where:
x = some expression

These are not necessarily competing, but they are relevant.

Nor have you done a review of any other languages, to see what similar 
features they already offer. Not even the C's form of "assignment as an 
expression" -- you should refer to that, and explain why this would not 
similarly be a bug magnet.



> Rationale
> =
> 
> When a subexpression is used multiple times in a list comprehension,

I think that list comps are merely a single concrete example of a more 
general concept that we sometimes want or need to apply the DRY 
principle to a single expression.

This is (usually) a violation of DRY whether it is inside or outside of 
a list comp:

result = (func(x), func(x)+1, func(x)*2)


> Syntax and semantics
> 
> 
> In any context where arbitrary Python expressions can be used, a **named
> expression** can appear. This must be parenthesized for clarity, and is of
> the form ``(expr as NAME)`` where ``expr`` is any valid Python expression,
> and ``NAME`` is a simple name.
>
> The value of such a named expression is the same as the incorporated
> expression, with the additional side-effect that NAME is bound to that
> value for the remainder of the current statement.


Examples should go with the description. Such as:

x = None if (spam().ham as eggs) is None else eggs

y = ((spam() as eggs), (eggs.method() as cheese), cheese[eggs])


> Just as function-local names shadow global names for the scope of the
> function, statement-local names shadow other names for that statement.
> (They can technically also shadow each other, though actually doing this
> should not be encouraged.)

That seems weird.


> Assignment to statement-local names is ONLY through this syntax. Regular
> assignment to the same name will remove the statement-local name and
> affect the name in the surrounding scope (function, class, or module).

That seems unnecessary. Since the scope only applies to a single 
statement, not a block, there can be no other assignment to that name.

Correction: I see further in that this isn't the case. But that's deeply 
confusing, to have the same name refer to two (or more!) scopes in the 
same block. I think that's going to lead to some really confusing 
scoping problems.



> Statement-local names never appear in locals() or globals(), and cannot be
> closed over by nested functions.

Why can they not be used in closures? I expect that's going to cause a 
lot of frustration.



> Execution order and its consequences
> 
> 
> Since the statement-local name binding lasts from its point of execution
> to the end of the current statement, this can potentially cause confusion
> when the actual order of execution does not match the programmer's
> expectations. Some examples::
> 
> # A simple statement ends at the newline or semicolon.
> a = (1 as y)
> print(y) # NameError

That error surprises me. Every other use of "as" binds to the 
current local namespace. (Or global, if you use the global 
declaration first.)

I think there's going to be a lot of confusion about which uses of "as" 
bind to a new local and which don't.

I think this proposal is conflating two unrelated concepts:

- introducing new variables in order to meet DRY requirements;

- introducing a new scope.

Why can't we do the first without the second?

a = (1 as y)
print(y)  # prints 1, as other uses of "as" would do


That would avoid the unnecessary (IMO) restriction that these variables 
cannot be used in closures.


> # The assignment ignores the SLNB - this adds one to 'a'
> a = (a + 1 as a)

"SLNB"? Undefined acronym. What is it? I presume it has something to do 
with the single-statement variable.


I know it would be legal, but why would you write something like that? 
Surely your examples must at least have a pretence of being useful (even 
if the examples are only toy 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Rhodri James

On 23/03/18 10:01, Chris Angelico wrote:

Apologies for letting this languish; life has an annoying habit of
getting in the way now and then.

Feedback from the previous rounds has been incorporated. From here,
the most important concern and question is: Is there any other syntax
or related proposal that ought to be mentioned here? If this proposal
is rejected, it should be rejected with a full set of alternatives.


Thank you very much, Chris.  I think you've won me over on most points, 
though I'm not sure whether I'm overall +0 or -0 on the whole 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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Chris Angelico
On Fri, Mar 23, 2018 at 9:38 PM, Paul Moore  wrote:
> On 23 March 2018 at 10:01, Chris Angelico  wrote:
>> # ... except when function bodies are involved...
>> if (input("> ") as cmd):
>> def run_cmd():
>> print("Running command", cmd) # NameError
>>
>> # ... but function *headers* are executed immediately
>> if (input("> ") as cmd):
>> def run_cmd(cmd=cmd): # Capture the value in the default arg
>> print("Running command", cmd) # Works
>
> What about
>
> cmd = "Something else"
> if (input("> ") as cmd):
> def run_cmd():
> print("Running command", cmd) # Closes over the "outer"
> cmd, not the statement-local one?
>
> Did I get that right? I don't really like it if so (I think it's
> confusing) but I guess I could live with "well, don't do that then" as
> an answer. And I don't have a better interpretation.

Yes, that would be it. And I agree: Don't do that. It's the same sort
of confusion you'd get here:

def f():
spam = 1
class C:
spam = 2
def g(x=spam):
print(spam) # prints 1
print(x) # prints 2
C.g()

A class creates a scope that function bodies inside it don't close
over, but their headers are still executed in that scope. So default
argument values "see" those inner variables, but the body of the
function doesn't. It's the same with SLNBs.

> I'm still not convinced I like the proposal, but it's a lot cleaner
> than previous versions, so thanks for that. Far fewer places where I
> said "hmm, I don't understand the implications".

Cool, thanks. That's the idea here.

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Paul Moore
On 23 March 2018 at 10:01, Chris Angelico  wrote:
> # ... except when function bodies are involved...
> if (input("> ") as cmd):
> def run_cmd():
> print("Running command", cmd) # NameError
>
> # ... but function *headers* are executed immediately
> if (input("> ") as cmd):
> def run_cmd(cmd=cmd): # Capture the value in the default arg
> print("Running command", cmd) # Works

What about

cmd = "Something else"
if (input("> ") as cmd):
def run_cmd():
print("Running command", cmd) # Closes over the "outer"
cmd, not the statement-local one?

Did I get that right? I don't really like it if so (I think it's
confusing) but I guess I could live with "well, don't do that then" as
an answer. And I don't have a better interpretation.

I'm still not convinced I like the proposal, but it's a lot cleaner
than previous versions, so thanks for that. Far fewer places where I
said "hmm, I don't understand the implications".

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] PEP 572: Statement-Local Name Bindings, take three!

2018-03-23 Thread Chris Angelico
Apologies for letting this languish; life has an annoying habit of
getting in the way now and then.

Feedback from the previous rounds has been incorporated. From here,
the most important concern and question is: Is there any other syntax
or related proposal that ought to be mentioned here? If this proposal
is rejected, it should be rejected with a full set of alternatives.

Text of PEP is below; formatted version will be live shortly (if it
isn't already) at:

https://www.python.org/dev/peps/pep-0572/

ChrisA

PEP: 572
Title: Syntax for Statement-Local Name Bindings
Author: Chris Angelico 
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 28-Feb-2018
Python-Version: 3.8
Post-History: 28-Feb-2018, 02-Mar-2018, 23-Mar-2018


Abstract


Programming is all about reusing code rather than duplicating it.  When
an expression needs to be used twice in quick succession but never again,
it is convenient to assign it to a temporary name with small scope.
By permitting name bindings to exist within a single statement only, we
make this both convenient and safe against name collisions.


Rationale
=

When a subexpression is used multiple times in a list comprehension, there
are currently several ways to spell this, none of which is universally
accepted as ideal. A statement-local name allows any subexpression to be
temporarily captured and then used multiple times.

Additionally, this syntax can in places be used to remove the need to write an
infinite loop with a ``break`` in it.  Capturing part of a ``while`` loop's
condition can improve the clarity of the loop header while still making the
actual value available within the loop body.


Syntax and semantics


In any context where arbitrary Python expressions can be used, a **named
expression** can appear. This must be parenthesized for clarity, and is of
the form ``(expr as NAME)`` where ``expr`` is any valid Python expression,
and ``NAME`` is a simple name.

The value of such a named expression is the same as the incorporated
expression, with the additional side-effect that NAME is bound to that
value for the remainder of the current statement.

Just as function-local names shadow global names for the scope of the
function, statement-local names shadow other names for that statement.
(They can technically also shadow each other, though actually doing this
should not be encouraged.)

Assignment to statement-local names is ONLY through this syntax. Regular
assignment to the same name will remove the statement-local name and
affect the name in the surrounding scope (function, class, or module).

Statement-local names never appear in locals() or globals(), and cannot be
closed over by nested functions.


Execution order and its consequences


Since the statement-local name binding lasts from its point of execution
to the end of the current statement, this can potentially cause confusion
when the actual order of execution does not match the programmer's
expectations. Some examples::

# A simple statement ends at the newline or semicolon.
a = (1 as y)
print(y) # NameError

# The assignment ignores the SLNB - this adds one to 'a'
a = (a + 1 as a)

# Compound statements usually enclose everything...
if (re.match(...) as m):
print(m.groups(0))
print(m) # NameError

# ... except when function bodies are involved...
if (input("> ") as cmd):
def run_cmd():
print("Running command", cmd) # NameError

# ... but function *headers* are executed immediately
if (input("> ") as cmd):
def run_cmd(cmd=cmd): # Capture the value in the default arg
print("Running command", cmd) # Works

Function bodies, in this respect, behave the same way they do in class scope;
assigned names are not closed over by method definitions. Defining a function
inside a loop already has potentially-confusing consequences, and SLNBs do not
materially worsen the existing situation.


Differences from regular assignment statements
--

Using ``(EXPR as NAME)`` is similar to ``NAME = EXPR``, but has a number of
important distinctions.

* Assignment is a statement; an SLNB is an expression whose value is the same
  as the object bound to the new name.
* SLNBs disappear at the end of their enclosing statement, at which point the
  name again refers to whatever it previously would have.  SLNBs can thus
  shadow other names without conflict (although deliberately doing so will
  often be a sign of bad code).
* SLNBs cannot be closed over by nested functions, and are completely ignored
  for this purpose.
* SLNBs do not appear in ``locals()`` or ``globals()``.
* An SLNB cannot be the target of any form of assignment, including augmented.
  Attempting to do so will remove the SLNB and assign to the fully-scoped name.

In many respects, an SLNB is akin to a local variable in