unintuitive for-loop behavior

Chris Angelico rosuav at gmail.com
Sun Oct 2 06:41:43 EDT 2016


On Sun, Oct 2, 2016 at 9:20 PM, Steve D'Aprano
<steve+python at pearwood.info> wrote:
> On Sun, 2 Oct 2016 04:06 pm, Chris Angelico wrote:
>> Hmm, interesting. I don't have IronPython here, but maybe you can tell
>> me what this does:
>>
>> print(str)
>> str = "demo"
>> print(str)
>> del str
>> print(str)
>>
>> and the same inside a function. In CPython, the presence of 'str =
>> "demo"' makes str function-local, ergo UnboundLocalError on the first
>> reference; but globals quietly shadow built-ins, so this will print
>> the class, demo, and the class again. If IronPython locals behave the
>> way CPython globals behave, that would most definitely be a
>> user-visible change to shadowing semantics.
>
> That's a nice catch!
>
> But its not a difference between "update binding" versus "new binding" --
> its a difference between LOAD_FAST and LOAD_whatever is used for things
> besides locals.

The only way to prove that something is a new binding is to
demonstrate that, when this binding is removed, a previous one becomes
visible. In Python, that only ever happens across namespaces, and in
CPython, the only way I can make it happen is with globals->builtins.

> steve at orac:~$ ipy
> IronPython 2.6 Beta 2 DEBUG (2.6.0.20) on .NET 2.0.50727.1433
> Type "help", "copyright", "credits" or "license" for more information.
>>>> def func():
> ...     print(str)
> ...     str = 1
> ...
>>>> func()
> Traceback (most recent call last):
> UnboundLocalError: Local variable 'str' referenced before assignment.

Beautiful, thank you.

> Although IronPython does the same thing as CPython here, I'm not 100% sure
> that this behaviour would be considered language specification or a mere
> guideline. If the author of an alternate implementation wanted to specify a
> single name resolution procedure:
>
> - look for local variable;
> - look for nonlocal;
> - look for global;
> - look for builtin;
> - fail
>
> rather than two:
>
> (1)
> - look for local;
> - fail;
>
> (2)
> - look for nonlocal;
> - look for global;
> - look for builtin;
> - fail
>
>
> I'm not entirely sure that Guido would say No. I think that "fail early if
> the local doesn't exist" as CPython does would be permitted rather than
> mandatory. But I could be wrong.

Interesting. The real question, then, is: Are function-local names
inherently special? I can't make anything else do this. Class
namespaces don't shadow globals or builtins, even during construction:

>>> class X:
...    str = "test"
...    def func(self):
...        print(self)
...        return str
...    print("123 =", func(123))
...    print("str =", func(str))
...
123
123 = <class 'str'>
test
str = <class 'str'>
>>> X().func()
<__main__.X object at 0x7f98643cc2b0>
<class 'str'>

The 'str' as a function parameter uses the class's namespace, but in
the function, no sir. (As it should be.) I don't know of any other
namespaces to test.

> By the way, here's an example showing that IronPython does allowing writing
> to locals to affect the local namespace:
>
>>>> def func():
> ...     locals()['x'] = 1
> ...     print(x)
> ...     if False:
> ...         x = 9999
> ...
>>>> func()
> 1
>
> And *that* behaviour is most definitely allowed -- the fact that writing to
> locals() isn't supported by CPython is most definitely an implementation-
> specific limitation, not a feature.

Yep. Would the same have happened if you'd omitted the "if False"
part, though - that is, if 'x' were not a local name due to
assignment, could it *become* local through mutation of locals()?

I would just grab IronPython and test, except that it isn't available
in the current Debian repositories:

https://archive.debian.net/search?searchon=names&keywords=ironpython

This most likely means I'll have to do some fiddling around with deps
to get it to install :(

ChrisA



More information about the Python-list mailing list