Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 840 – Name Resolution in Class Namespaces

PEP 840 – Name Resolution in Class Namespaces

Author:
Jeremy Hylton <jeremy at python.org>, Guido van Rossum <guido at python.org>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Created:
15-Jul-2026
Python-Version:
3.16
Post-History:
16-Jul-2026

Table of Contents

Abstract

There is a long-standing inconsistency in the way variable names are resolved in classes. Several alternatives to resolve this inconsistency are discussed.

Movtivation

Name resolution in a class namespace uses a partial dynamic lookup. A name can be either local or global depending on whether an assignment to the name has occured during the exection of the class body. If a name is used before assignment, it will be looked up in globals (and builtins).

If a class is defined within a function scope, a name may be resolved to the nearest enclosing scope. If name local to an enclosing scope, but the name is not bound, a NameError will occur.

The apparent consistency problem is that names may be both local and global in the same class block, but they cannot be both local and free. Or, name resolution behaves differently in a class depending on whether it is defined at the top-level or inside another block. If a global variable exists, it can be used to resolve a free variable unless that variable is bound in an enclosing scope of the class.

Here is an example of resolution of a free variable in a class:

>>> x = 0
>>> def f(x):
...     class A:
...         a = x
...     return A
...
>>> A = f(1)
>>> A.a  # the value of the parameter x, not the global x
1

The variable x is resolved to the parameter of the function x. The global is ignored. This behavior is the standard name resolution defined in PEP 227 and PEP 3104.

If x is used as a local variable, the behavior is different at the top-level and inside another block. If the same code also uses x as a local variable, then the initial reference to x is resolved differently.

>>> x = 0
>>> def f(x):
...     class A:
...         a = x
...         x = 42
...     return A
...
>>> A = f(1)
>>> A.a  # the value of the global x, not the parameter x
0

The first reference to x is no longer bound to the local variable x defined in f, but instead uses the global variable.

The set of namespaces consulted are determined lexically, but it cannot be determined statically whether a particular reference will be resolved using the global namespace.

>>> x = 0
>>> def f(var):
...     class A:
...         x = 10
...         if var in locals():
...             del locals()[var]
...         a = x
...     return A
...
>>> A1 = f("x")
>>> A2 = f("y")
>>> A1.a, A2.a
(0, 10)

Background

The Python 2.0 definition specifies exactly three namespaces to check for each name – the local namespace, the global namespace, and the builtin namespace. Starting with Python 2.1, lexical scoping was used and free variables could be resolved to a binding in an enclosing scope. Class namespaces were a special case, and free variables were not resolved in enclosing scopes. They used special LOAD_NAME / STORE_NAME opcodes that explicit checked local and global namespaces.

In Python 3.4, free variables in classes were resolved in enclosing scopes, but a local variable used before assignment was still resolved in the global scope. The language specification was not updated at this time, so it still specifies the pre-3.4 behavior:

A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace.

Class namespaces and top-level module namespaces both use LOAD_NAME / STORE_NAME opcodes that reflect the early local / global / builtin scoping rules. Class namespaces are also special, because the class namespace become the attributes of the class object. Given these differences, the different scoping rules for classes weren’t considered when PEP 227 was written.

A bug was filed in 2002. The PEP author closed the bug with the comment, “Just don’t write code that abuses the wart.” https://github.com/python/cpython/issues/36300 Another bug was filed in 2010, leading the same author to suggest a change. https://github.com/python/cpython/issues/53472 Apparently, inconsistent behavior in namespaces can lead to inconsistent responses to identical bugs.

Proposal

There are several alternatives we could choose among. A few simple alternatives seem wrong. We could revert to pre-3.4 behavior, but it was surprising the free variables did not work in class scopes. We could keep the current behavior, but it is seems inconsistent.

One approach is to make class namespaces work more like function namespaces. If a local variable is used at a time when it is not bound, it raises a NameError. This rule is simple and consistent. The primary drawback of this approach is that it will break code. Code that depends on this feature is inscrutable, depending on the reader to understand whether the local or global reference was intended. Inscrutable, though, is not the same as undefined or broken

Another approach is to change the behavior of free variable resolution to use the global namespaces when a name is unbound. If current code would raise a NameError, because a variable was unbound in the enclosing scope, it would be resolved in the global namespace.

There is a third approach that is a subtle variant of the second. If a local variable is unbound, and that variable has a binding in an enclosing scope, use that namespace rather than the global namespace.

>>> x = 0
>>> def f():
...     x = 1
...     class A:
...         x = 2
...         del x
...         a = x # Should this use f's local or global?
...     return A
...
>>> A = f()
>>> A.a
?

If a local variable is unbound and it can be resolved in another namespace anyway, why only the global namespace? Why not use the full set of standard name resolution rules?

This option seems most consistent, but makes the implementation more complicated. It can’t be statically determinted whether a variable is bound at a particular point, so we would need to allocate a cell for any local variable of the class that shadows a local variable in an enclosing scope. Since dynamic manipulation of local variables is unusual, we would expect the extra closures required to almost always be unused. It would nonetheless be a somewhat unusual case– a nested class definition that has a local variable that shadows a local variable in an enclosing scope.