Back to Blog
Python

Python Class Namespace: How Attribute Lookup Works

Understand how the python class namespace works, how attribute lookup resolves names, and how class and instance namespaces differ in real code.

python classesattribute lookupnamespacemethod resolution orderinstance attributes
Diagram showing a Python class namespace with attribute lookup flowing from instance to class to base classes

What a Class Namespace Actually Is

When you define a class in Python, the body of the class executes in its own namespace. Understanding the python class namespace is essential for predicting how attribute access behaves, how inheritance resolves names, and why certain bugs appear only when instances share mutable defaults.

When you write:

class Counter: total = 0 label = "counter"

The names total and label live in the class namespace. Accessing Counter.total retrieves the value from that namespace. The class namespace is distinct from the module namespace that surrounds the class definition, and it is also distinct from the namespace of any instance created from the class.

This separation matters because it determines where Python looks for a name when you access an attribute. The lookup order is well-defined: instance namespace first, then class namespace, then any base classes in the method resolution order.

How Instance and Class Namespaces Differ

Every instance of a class has its own __dict__ that holds instance attributes. The class itself also has a __dict__ that holds class attributes. When you assign self.total = 5 inside a method, you are writing to the instance namespace, not the class namespace. When you assign Counter.total = 10 at module level, you are writing to the class namespace.

class Counter: total = 0 c1 = Counter() c2 = Counter() c1.total = 5 print(c1.total) # 5 - instance namespace print(c2.total) # 0 - class namespace print(Counter.total) # 0 - class namespace

The instance c1 now has its own total attribute. The class namespace still holds total = 0. The instance c2 has no instance-level total, so Python falls back to the class namespace.

This behavior is a common source of confusion. Assigning to an attribute through an instance does not modify the class attribute; it shadows it. The class namespace remains unchanged unless you explicitly assign through the class object itself.

How Methods Resolve Names Through the Class Namespace

When a method references a name that is not a local variable and not an argument, Python resolves it through the instance and class namespaces. Consider this example:

class Config: timeout = 30 def get_timeout(self): return self.timeout

The method get_timeout does not define timeout as a local variable. Python looks for it in the instance namespace first, then in the class namespace. If no instance attribute exists, the class attribute timeout is returned.

This lookup mechanism is what makes class attributes useful as defaults. An instance can override the default by assigning its own attribute, and the method will pick up the instance value. If no override exists, the class default applies.

The same resolution applies to methods themselves. A method defined in the class body is stored in the class namespace as a function object. Accessing it through an instance produces a bound method, which automatically passes the instance as the first argument.

The Method Resolution Order and Inheritance

When a class inherits from another class, attribute lookup extends beyond the immediate class namespace. Python consults the method resolution order, which is the sequence of classes that Python checks when looking up an attribute.

class Base: kind = "base" class Derived(Base): pass d = Derived() print(d.kind) # "base" - found in Base's namespace

The instance d has no kind in its own namespace. Derived has no kind in its namespace either. Python follows the MRO to Base and finds it there.

The MRO is computed using the C3 linearization algorithm. For simple single-inheritance hierarchies, the order is straightforward: the class itself, then its parent, then the grandparent. For multiple inheritance, the order is more complex and follows the C3 rules.

Understanding the MRO matters when you override attributes in subclasses. If a subclass defines an attribute with the same name as a base class attribute, the subclass's namespace takes precedence during lookup.

Common Mistakes with Class Namespaces

One frequent mistake is using a mutable class attribute as a shared default. Because the class namespace is shared across all instances, a mutable value stored there is visible to every instance.

class Registry: items = [] r1 = Registry() r2 = Registry() r1.items.append("first") print(r2.items) # ["first"] - shared through class namespace

Both instances see the same list because items lives in the class namespace. If the intent was to give each instance its own list, the attribute should be initialized in __init__ instead.

Another mistake is assuming that assigning through an instance updates the class namespace. It does not. The assignment creates an instance attribute that shadows the class attribute. To update the class-level value, assign through the class object.

A third mistake is relying on class attribute lookup inside methods when the attribute name collides with a local variable or argument. Python's name resolution inside a method follows the local scope first, then enclosing scopes, then global scope, then builtins. Class attributes are not part of this lexical scope chain; they are only reachable through self or the class object.

Runtime Cost of Attribute Lookup

Attribute lookup through the instance and class namespaces has a runtime cost. Every access goes through several dictionary lookups: the instance __dict__, then the class __dict__, then each class in the MRO. For code executed in hot loops, this cost is measurable, though usually small.

Python's attribute access is implemented in the LOAD_ATTR bytecode instruction. The interpreter performs the lookup dynamically. There is no compile-time resolution of attribute names, which is why attribute access is slower than local variable access.

When performance matters, developers sometimes cache attribute lookups in local variables:

def process(self, items): limit = self.limit for item in items: if item > limit: ...

This reads self.limit once and avoids repeated namespace lookups inside the loop. The behavior is identical, but the local variable access is faster.

Class Namespaces and Maintainability

The distinction between class and instance namespaces has direct consequences for code maintainability. Class attributes are shared state. If any code path mutates a mutable class attribute, the change is visible to all instances and to all code that reads the class attribute directly.

For configuration-like values that should not change, consider using immutable types or naming conventions that signal intent. For per-instance state, always initialize in __init__.

When subclassing, be aware that a subclass inherits the parent's class namespace. Overriding a class attribute in the subclass creates a new entry in the subclass's namespace, which shadows the parent's value for instances of the subclass. Instances of the parent remain unaffected.

This behavior is useful for defining defaults that subclasses can override, but it can also hide bugs. If a subclass accidentally assigns to a class attribute name that the parent uses internally, the parent's methods will see the subclass's value when called on a subclass instance.

Using __dict__ to Inspect Namespaces

Python exposes the namespace dictionaries directly. The class namespace is accessible through ClassName.__dict__, and the instance namespace through instance.__dict__.

class Sample: value = 42 s = Sample() s.extra = "added" print(Sample.__dict__) print(s.__dict__)

The class __dict__ includes value along with method objects and other class-level entries. The instance __dict__ contains only extra, because value was never assigned through the instance.

Inspecting __dict__ is useful for debugging and for metaprogramming. Frameworks that build classes dynamically, such as ORMs and serialization libraries, read and write these dictionaries to map class definitions to external schemas.

One caution: __dict__ is a mapping proxy for classes, not a regular dictionary. You can read from it, but you cannot mutate it directly. To modify the class namespace, use setattr or assign through the class object.

python class namespace: Practical Usage and Code Examples | RYUSLOG DEV