classification
Title: Incorrect (misleading) statement in the execution model documentation
Type: enhancement Stage: resolved
Components: Documentation Versions: Python 3.6, Python 3.5, Python 3.4
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: docs@python Nosy List: abacabadabacaba, arigo, docs@python, eric.snow, levkivskyi, ncoghlan, r.david.murray, rhettinger
Priority: normal Keywords: patch

Created on 2015-05-05 20:51 by levkivskyi, last changed 2015-08-05 14:55 by ncoghlan. This issue is now closed.

Files
File name Uploaded Description Edit
classdoc.patch levkivskyi, 2015-05-15 08:44 Imptoved wording for docs on execution model (class statement) review
classdoc_v2.patch levkivskyi, 2015-06-19 22:50 Made changes sugested by Eric review
classdoc-v3.patch levkivskyi, 2015-06-20 13:55 A bit more extensive patch; main improvment is separating doc into subsections review
classdoc-v4.patch levkivskyi, 2015-06-21 13:57 Comments of Nick and Eric are taken into account. review
Messages (18)
msg242619 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-05-05 20:51
The documentation on execution model https://docs.python.org/3/reference/executionmodel.html contains the statement
"""
A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution. The namespace of the class definition becomes the attribute dictionary of the class. Names defined at the class scope are not visible in methods.
"""
However, the following code (taken from http://lackingrhoticity.blogspot.ch/2008/08/4-python-variable-binding-oddities.html):

x = "xtop"
y = "ytop"
def func():
    x = "xlocal"
    y = "ylocal"
    class C:
        print(x)
        print(y)
        y = 1
func()

prints

xlocal
ytop

In case of "normal rules for name resolution" it should rise UnboundLocalError.

I suggest replacing the mentioned statement with the following:
"""
A class definition is an executable statement that may use and define names. Free variables follow the normal rules for name resolution, bound variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. Names defined at the class scope are not visible in methods.
"""
or a similar one.
msg243258 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-05-15 08:44
Since no one proposed alternative ideas, I am submitting my proposal as a patch, with the following wording:

"""
A class definition is an executable statement that may use and define names. Free variables follow the normal rules for name resolution, while unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. Names defined at the class scope are not visible in methods
"""
msg245514 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-19 19:22
Should I invite someone to review the patch or just wait? How the things are organized here?
msg245517 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-06-19 19:46
In this particular case, just wait (now that you have pinged the issue).  Raymond is the most likely person to figure out how to phrase this better, but it isn't obvious what the best way to explain this is.  I don't think your explanation is exactly correct, but I don't know enough about how class name resolution is implemented to explain what's wrong with it, I just know it doesn't feel quite right :)  (Of course, I might be wrong.)

Ping the issue again in a few weeks if there is no action.
msg245524 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-06-19 19:58
I've left a review.  That said, we need to be sure this behavior is intentional.  The fact that it skips the "nonlocal" scope(s) smells like a bug to me.
msg245533 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-19 22:50
Eric, thank you for the review. I have incorporated proposed changes in second version of the patch.
Concerning the question whether it is a bug, it also smells like a bug to me, but Guido said 13 years ago that this should not be changed: https://mail.python.org/pipermail/python-dev/2002-April/023428.html and it stayed like this since then. However, things changed a bit in Python 3.4 with the introduction of the LOAD_CLASSDEREF opcode. Perhaps, we should ask Guido again :) What do you think?
msg245535 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2015-06-19 23:18
I expect you'll get the same response, especially given potential (though slight) chance for backward-compatibility issues.  What I find curious is Guido's reference to "the rule that class bodies don't play the nested
scopes game" (and his subsequent explanation).  Is there something about that in the language reference?  If so, the patch should be updated to link to that section.  If not then it should be added to the language reference.

That said, it wouldn't hurt to ask on python-dev, particularly in light of that new opcode.
msg245545 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2015-06-20 07:02
The "normal rules for name resolution" reference here is referring to the name lookup rules as they existed prior to the introduction of lexical scoping for functions. It's a dated way of describing it, as the current behaviour of functions has now been around long enough that a lot of folks will consider *that* normal, and the module, class and exec scoping rules to be the unusual case (as levkivskyi has here).

However, I've spent far too many hours staring at CPython compiler internals to be able to suggest a helpful rewording that will make sense to folks that *haven't* done that, so I'll instead provide the relevant background info to see if others can come up with a concise rewording of the reference docs :)

Prior to Python 2.1, Python didn't have closure support, and hence nested functions and classes couldn't see variables in outer scopes at all - they could see their local scope, the module globals, and the builtins. That changed with the introduction of nested scopes as a __future__ import in Python 2.1 and the default behaviour in 2.2: https://www.python.org/dev/peps/pep-0227/

As a result of that change, the compiler now keeps track of "function locals" at compile time, and *emits different code for references to them*. Where early versions of CPython only had LOAD_NAME and LOAD_GLOBAL in the bytecode, these days we now also have LOAD_FAST (function local), LOAD_CLOSURE (function local referenced as a nonlocal), LOAD_DEREF (function nonlocal) and LOAD_CLASSDEREF (class nonlocal). The latter four opcodes will *only* be emitted in a function body - they'll never be emitted for module level code (include the bodies of module level class definitions). If you attempt to reference a function local before a value has been assigned, you'll get UnboundLocalError rather than NameError.

The name lookup rules used for execution of class bodies are thus the same ones used for the exec() builtin with two namespace arguments: there is a local namespace where name assignments happen, and name lookups check the local, global and builtin namespaces in that order. The code is executed line by line, so if a name is referenced before it has been assigned locally, then it may find a global or builtin of that name. Classes that are defined inside a function may refer to lexically scoped local variables from the class body, but class variables are not themselves visible to function definitions nested inside a class scope (i.e. method definitions).

These rules are also used for module level execution and exec() with a single namespace argument, except that the local namespace and the global namespace refer to the same namespace.
msg245548 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-20 07:14
Eric, the "rule" that classes don't play the nested scopes game is explained at beginning of the same section, but the explanation is "one sided" it only explains that names defined in classes are not visible inside functions.
Nick, thank you for the thorough explanation. I will try to improve the wording. It looks like a bit more substantial changes are needed.
msg245554 - (view) Author: Armin Rigo (arigo) * (Python committer) Date: 2015-06-20 08:24
Related to http://bugs.python.org/issue19979 and others mentioned there.
msg245562 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-20 13:55
Eric, I have submitted a new version of the patch. Could you please make a review? Nick, it will be interesting to hear your opinion too.

I tried to follow such rules:
1. Explanation should be succinct yet clear
2. It should tell as less as possible about implementation details
3. Minimize necessary changes

It turns out that these goals could be achieved by 
a) simply reshuffling and structuring the existing text to separate the exceptions (classes, etc.) from the general case;
and
b) adding some minor clarifications.

Armin, thank you for the link. It looks like this is a really old discussion.

PS: Unfortunately, the diff after reshuffling of the text looks big and cumbersome, in fact the changes are minimal.
msg245596 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-21 13:57
Nick, thank you for a review, I have made a new patch with all the previous comments taken into account.
msg245697 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-06-23 19:09
It looks like on python-dev (http://www.mail-archive.com/python-dev@python.org/msg88256.html) there is an agreement that this behavior should not be changed (at least not in the nearest future). If there are no more comments/suggestions, then maybe one could accept the latest patch?
msg246031 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-07-01 08:41
What holds the patch now? Should I do something or just wait?
msg247800 - (view) Author: Ivan Levkivskyi (levkivskyi) * Date: 2015-08-01 08:14
I am sorry but I still don't get how things are organized here, so pinging this up. What is the next step? Should I wait for another review?
msg247938 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-08-03 17:03
Your ping after a month is very appropriate.  It looks like yes, this is waiting for another review.  Based on the fact that the previous patches were reviewed by core devs and you have responded, I'm moving it to 'commit review', but I haven't looked at the patch myself.
msg248039 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2015-08-05 13:43
I merged Ivan's latest patch to 3.4/3.5/default. We're unlikely to ever be able to make these docs completely intuitive (as name resolution is genuinely complex), but Ivan's revisions at least mean we're no longer assuming readers know how the name resolution worked prior to the introduction of lexical scoping, and a couple of tricky cases now have inline examples.

I also noticed an existing paragraph in the docs that *I* didn't understand, and filed issue #24796 to cover that. I'm not sure if we should just delete the paragraph, or if we accidentally dropped a compile time error check that didn't have any tests to ensure we were detecting the problem.
msg248045 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2015-08-05 14:55
The issue tracker was having issues and didn't automatically register the commits. Links:

3.4: https://hg.python.org/cpython/rev/94e215a5e24b
3.5: https://hg.python.org/cpython/rev/5e4d21311772
default: https://hg.python.org/cpython/rev/e75881393cf2
History
Date User Action Args
2015-08-05 14:55:27ncoghlansetmessages: + msg248045
2015-08-05 14:53:17ncoghlansetmessages: - msg248043
2015-08-05 14:51:51ncoghlansetmessages: + msg248043
2015-08-05 14:47:23ncoghlansetstatus: open -> closed
type: behavior -> enhancement
messages: + msg248039

resolution: fixed
stage: commit review -> resolved
2015-08-03 17:03:08r.david.murraysetstage: commit review
messages: + msg247938
versions: + Python 3.6
2015-08-01 08:14:56levkivskyisetmessages: + msg247800
2015-07-01 08:41:04levkivskyisetmessages: + msg246031
2015-06-23 19:09:32levkivskyisetmessages: + msg245697
2015-06-22 18:56:26abacabadabacabasetnosy: + abacabadabacaba
2015-06-21 13:57:11levkivskyisetfiles: + classdoc-v4.patch

messages: + msg245596
2015-06-20 13:55:42levkivskyisetfiles: + classdoc-v3.patch

messages: + msg245562
2015-06-20 08:24:30arigosetnosy: + arigo
messages: + msg245554
2015-06-20 07:14:09levkivskyisetmessages: + msg245548
2015-06-20 07:02:57ncoghlansetmessages: + msg245545
2015-06-19 23:18:56eric.snowsetnosy: + ncoghlan
messages: + msg245535
2015-06-19 22:50:16levkivskyisetfiles: + classdoc_v2.patch

messages: + msg245533
2015-06-19 19:58:54eric.snowsetnosy: + eric.snow
messages: + msg245524
2015-06-19 19:47:00r.david.murraysetnosy: + r.david.murray
messages: + msg245517
2015-06-19 19:22:08levkivskyisetmessages: + msg245514
2015-05-15 08:44:09levkivskyisetfiles: + classdoc.patch
keywords: + patch
messages: + msg243258
2015-05-08 20:51:20rhettingersetnosy: + rhettinger
2015-05-05 20:51:59levkivskyicreate