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