[Python-ideas] PEP 572: Statement-Local Name Bindings

Ethan Furman ethan at stoneleaf.us
Wed Feb 28 10:10:43 EST 2018


On 02/27/2018 09:23 PM, Chris Angelico wrote:
> On Wed, Feb 28, 2018 at 2:47 PM, Rob Cliffe via Python-ideas wrote:

>> And here's a thought: What are the semantics of
>>      a = (42 as a) # Of course a linter should point this out too
>> At first I thought this was also a laborious synonym for "a=42".  But then I
>> re-read your statement (the one I described above as crystal-clear) and
>> realised that its exact wording was even more critical than I had thought:
>>      "the new name binding will shadow the other name from the point where it
>> is evaluated until the end of the statement"
>> Note: "until the end of the statement".  NOT "until the end of the
>> expression".  The distinction matters.
>> If we take this as gospel, all this will do is create a temporary variable
>> "a", assign the value 42 to it twice, then discard it.  I.e. it effectively
>> does nothing, slowly.
>> Have I understood correctly?  Very likely you have considered this and mean
>> exactly what you say, but I am sure you will understand that I mean no
>> offence by querying it.
>
> Actually, that's a very good point, and I had to actually go and do
> that to confirm. You're correct that the "a =" part is also affected,
> but there may be more complicated edge cases. Disassembly can help
> track down what the compiler's actually doing:
>
>>>> def f():
> ...     a = 1
> ...     a = (2 as a)
> ...     print(a)
> ...
>>>> dis.dis(f)
>    2           0 LOAD_CONST               1 (1)
>                2 STORE_FAST               0 (a)
>
>    3           4 LOAD_CONST               2 (2)
>                6 DUP_TOP
>                8 STORE_FAST               1 (a)
>               10 STORE_FAST               1 (a)
>               12 DELETE_FAST              1 (a)
>
>    4          14 LOAD_GLOBAL              0 (print)
>               16 LOAD_FAST                0 (a)
>               18 CALL_FUNCTION            1
>               20 POP_TOP
>               22 LOAD_CONST               0 (None)
>               24 RETURN_VALUE
>
> If you're not familiar with the output of dis.dis(), the first column
> (largely blank) is line numbers in the source, the second is byte code
> offsets, and then we have the operation and its parameter (if any).
> The STORE_FAST and LOAD_FAST opcodes work with local names, which are
> identified by their indices; the first such operation sets slot 0
> (named "a"), but the two that happen in line 3 (byte positions 8 and
> 10) are manipulating slot 1 (also named "a"). So you can see that line
> 3 never touches slot 0, and it is entirely operating within the SLNB
> scope.

dis.dis may be great, but so is running the function so everyone can see the output.  ;)

If I understood your explanation, `print(a)` produces `1` ?  That seems wrong -- the point of statement-local name 
bindings is twofold:

- give a name to a value
- evaluate to that value

Which is why your first example works:

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

(f(x) as y), y

evaluates as f(x), and also assigns that result to y, so in

     a = (2 as a)

there is a temporary variable 'a', which gets assigned 2, and the SLNB is evaluated as 2, which should then get assigned 
back to the local variable 'a'.  In other words, the final print from `f()` above should be 2, not 1.  (Slightly 
different names would help avoid confusion when referencing different locations of the PEP.)

--
~Ethan~


More information about the Python-ideas mailing list