Skip to content

Commit 312bb9e

Browse files
Merge branch 'main' into konsti/pyvenv-cfg-python-version
2 parents 49d35ed + fde39a8 commit 312bb9e

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

‎.github/CODEOWNERS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ peps/pep-0833.rst @dstufft
712712
peps/pep-0835.rst @ilevkivskyi
713713
peps/pep-0836.rst @savannahostrowski @Fidget-Spinner @brandtbucher
714714
peps/pep-0838.rst @AlexWaygood
715+
peps/pep-0840.rst @jeremyhylton @gvanrossum
715716
# ...
716717
peps/pep-2026.rst @hugovk
717718
# ...

‎peps/pep-0840.rst‎

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
PEP: 840
2+
Title: Name Resolution in Class Namespaces
3+
Author: Jeremy Hylton <jeremy@python.org>,
4+
Guido van Rossum <guido@python.org>
5+
Discussions-To: https://discuss.python.org/t/pep-840-name-resolution-in-class-namespaces/108166
6+
Status: Draft
7+
Type: Standards Track
8+
Created: 15-Jul-2026
9+
Python-Version: 3.16
10+
Post-History: `16-Jul-2026 <https://discuss.python.org/t/pep-840-name-resolution-in-class-namespaces/108166>`__
11+
12+
13+
Abstract
14+
========
15+
16+
There is a long-standing inconsistency in the way variable names are
17+
resolved in classes. Several alternatives to resolve this
18+
inconsistency are discussed.
19+
20+
Movtivation
21+
===========
22+
23+
Name resolution in a class namespace uses a partial dynamic lookup. A
24+
name can be either local or global depending on whether an assignment
25+
to the name has occured during the exection of the class body. If a
26+
name is used before assignment, it will be looked up in globals (and
27+
builtins).
28+
29+
If a class is defined within a function scope, a name may be resolved
30+
to the nearest enclosing scope. If name local to an enclosing scope,
31+
but the name is not bound, a NameError will occur.
32+
33+
The apparent consistency problem is that names may be both local and
34+
global in the same class block, but they cannot be both local and
35+
free. Or, name resolution behaves differently in a class depending on
36+
whether it is defined at the top-level or inside another block. If a
37+
global variable exists, it can be used to resolve a free variable
38+
unless that variable is bound in an enclosing scope of the class.
39+
40+
Here is an example of resolution of a free variable in a class:
41+
42+
>>> x = 0
43+
>>> def f(x):
44+
... class A:
45+
... a = x
46+
... return A
47+
...
48+
>>> A = f(1)
49+
>>> A.a # the value of the parameter x, not the global x
50+
1
51+
52+
The variable x is resolved to the parameter of the function x. The
53+
global is ignored. This behavior is the standard name resolution
54+
defined in :pep:`227` and :pep:`3104`.
55+
56+
If x is used as a local variable, the behavior is different at the
57+
top-level and inside another block. If the same code also uses x as a
58+
local variable, then the initial reference to x is resolved
59+
differently.
60+
61+
>>> x = 0
62+
>>> def f(x):
63+
... class A:
64+
... a = x
65+
... x = 42
66+
... return A
67+
...
68+
>>> A = f(1)
69+
>>> A.a # the value of the global x, not the parameter x
70+
0
71+
72+
The first reference to x is no longer bound to the local variable x
73+
defined in f, but instead uses the global variable.
74+
75+
The set of namespaces consulted are determined lexically, but it
76+
cannot be determined statically whether a particular reference will be
77+
resolved using the global namespace.
78+
79+
>>> x = 0
80+
>>> def f(var):
81+
... class A:
82+
... x = 10
83+
... if var in locals():
84+
... del locals()[var]
85+
... a = x
86+
... return A
87+
...
88+
>>> A1 = f("x")
89+
>>> A2 = f("y")
90+
>>> A1.a, A2.a
91+
(0, 10)
92+
93+
94+
Background
95+
==========
96+
97+
The Python 2.0 definition specifies exactly three namespaces to
98+
check for each name -- the local namespace, the global namespace,
99+
and the builtin namespace. Starting with Python 2.1, lexical scoping
100+
was used and free variables could be resolved to a binding in an
101+
enclosing scope. Class namespaces were a special case, and free variables
102+
were not resolved in enclosing scopes. They used special LOAD_NAME /
103+
STORE_NAME opcodes that explicit checked local and global namespaces.
104+
105+
In Python 3.4, free variables in classes were resolved in enclosing scopes,
106+
but a local variable used before assignment was still resolved in the global
107+
scope. The language specification was not updated at this time, so it
108+
still specifies the pre-3.4 behavior:
109+
110+
A class definition is an executable statement that may use and define names.
111+
These references follow the normal rules for name resolution with an exception
112+
that unbound local variables are looked up in the global namespace.
113+
114+
Class namespaces and top-level module namespaces both use LOAD_NAME / STORE_NAME
115+
opcodes that reflect the early local / global / builtin scoping rules. Class
116+
namespaces are also special, because the class namespace become the attributes
117+
of the class object. Given these differences, the different scoping rules for
118+
classes weren't considered when :pep:`227` was written.
119+
120+
A bug was filed in 2002. The PEP author closed the bug with the
121+
comment, "Just don't write code that abuses the wart."
122+
https://github.com/python/cpython/issues/36300
123+
Another bug was filed in 2010, leading the same author to suggest a change.
124+
https://github.com/python/cpython/issues/53472
125+
Apparently, inconsistent behavior in namespaces can lead to
126+
inconsistent responses to identical bugs.
127+
128+
Proposal
129+
========
130+
131+
There are several alternatives we could choose among. A few simple
132+
alternatives seem wrong. We could revert to pre-3.4 behavior, but it
133+
was surprising the free variables did not work in class scopes. We
134+
could keep the current behavior, but it is seems inconsistent.
135+
136+
One approach is to make class namespaces work more like function
137+
namespaces. If a local variable is used at a time when it is not
138+
bound, it raises a NameError. This rule is simple and consistent. The
139+
primary drawback of this approach is that it will break code. Code
140+
that depends on this feature is inscrutable, depending on the reader
141+
to understand whether the local or global reference was
142+
intended. Inscrutable, though, is not the same as undefined or broken
143+
144+
Another approach is to change the behavior of free variable
145+
resolution to use the global namespaces when a name is unbound. If
146+
current code would raise a NameError, because a variable was unbound
147+
in the enclosing scope, it would be resolved in the global namespace.
148+
149+
There is a third approach that is a subtle variant of the second. If
150+
a local variable is unbound, and that variable has a binding in an
151+
enclosing scope, use that namespace rather than the global namespace.
152+
153+
>>> x = 0
154+
>>> def f():
155+
... x = 1
156+
... class A:
157+
... x = 2
158+
... del x
159+
... a = x # Should this use f's local or global?
160+
... return A
161+
...
162+
>>> A = f()
163+
>>> A.a
164+
?
165+
166+
If a local variable is unbound and it can be resolved in another
167+
namespace anyway, why only the global namespace? Why not use the
168+
full set of standard name resolution rules?
169+
170+
This option seems most consistent, but makes the implementation more
171+
complicated. It can't be statically determinted whether a variable is
172+
bound at a particular point, so we would need to allocate a cell for
173+
any local variable of the class that shadows a local variable in an
174+
enclosing scope. Since dynamic manipulation of local variables is
175+
unusual, we would expect the extra closures required to almost always
176+
be unused. It would nonetheless be a somewhat unusual case-- a nested
177+
class definition that has a local variable that shadows a local
178+
variable in an enclosing scope.
179+
180+
181+
Copyright
182+
=========
183+
184+
This document is placed in the public domain or under the
185+
CC0-1.0-Universal license, whichever is more permissive.

0 commit comments

Comments
 (0)