How types.DynamicClassAttribute Works in Python
Python's types.DynamicClassAttribute is a descriptor
that routes attribute access differently depending on whether the
attribute is read from an instance or from the class itself. While
accessing the attribute via an instance invokes a defined getter
function just like a standard @property, accessing it
directly from the class raises an AttributeError, allowing
the lookup to fall back to the metaclass's __getattr__
method. This article explains the internal mechanics of this descriptor,
why it is used, and how it enables dynamic class-level behavior in
Python libraries such as enum.
The Standard Property Limitation
To understand DynamicClassAttribute, consider how
Python's built-in @property behaves:
class Example:
@property
def value(self):
return 42
obj = Example()
print(obj.value) # Returns: 42
print(Example.value) # Returns: <property object at 0x...>When evaluated on an instance, a standard descriptor's
__get__(instance, owner) receives the instance and returns
the computed result. When evaluated on the class
(Example.value), instance is
None, and the descriptor returns self—the
property object itself.
This behavior creates a problem when a class needs to expose an
attribute on instances while allowing the class itself to handle that
same attribute name dynamically (for example, through a metaclass).
Because the property descriptor exists in Example.__dict__,
Python's lookup rules find it first, preventing any metaclass
fallback.
How DynamicClassAttribute Routes Access
types.DynamicClassAttribute modifies the descriptor
protocol (__get__, __set__,
__delete__) to bifurcate access between instances and
classes:
Instance Access (
instance is not None):
The descriptor acts like a standard@property. It invokes the instance getter (self.fget(instance)) and returns the resulting value.Class Access (
instance is None):
Instead of returning the descriptor object itself,DynamicClassAttribute.__get__raises anAttributeError(unless a custom class getter is explicitly set).
In Python, when a descriptor's __get__ method raises an
AttributeError during class-level attribute lookup
(Class.attribute), Python catches the exception and falls
back to searching the metaclass. If the metaclass defines a
__getattr__ method, Python calls it with the attribute
name.
The Enum Use Case
The primary motivation for types.DynamicClassAttribute
in Python's standard library is the enum module.
Every enum member has name and value
attributes:
from enum import Enum
class Status(Enum):
PENDING = "pending"
COMPLETED = "completed"Here, Status.PENDING is an instance of
Status. Accessing Status.PENDING.name returns
"PENDING".
However, what happens if an enum defines a member named
name or value?
class Fields(Enum):
name = "Field Name"
value = "Field Value"If name were a standard @property:
Fields.namewould return<property object>, colliding with the enum member namedname.
Because Enum defines name and
value as DynamicClassAttribute instances:
Fields.name.valueevaluatesFields.nameon the class level.- The descriptor raises
AttributeError. - Python delegates lookup to
EnumType.__getattr__. EnumTypesuccessfully resolves and returns the enum memberFields.name.- On that member instance,
.valueinvokesfget(instance)and returns"Field Name".
Practical Implementation Example
You can observe this routing mechanism directly by implementing a
custom metaclass alongside DynamicClassAttribute:
from types import DynamicClassAttribute
class Meta(type):
def __getattr__(cls, name):
return f"Metaclass fallback for attribute: {name}"
class CustomModel(metaclass=Meta):
def __init__(self, item):
self._item = item
@DynamicClassAttribute
def item(self):
return f"Instance item: {self._item}"
# Instance access routes to the getter function
instance = CustomModel("data_payload")
print(instance.item)
# Output: Instance item: data_payload
# Class access triggers AttributeError in the descriptor and falls back to Meta.__getattr__
print(CustomModel.item)
# Output: Metaclass fallback for attribute: itemBy purposefully failing class-level descriptor resolution,
types.DynamicClassAttribute hands control over to the
metaclass, allowing clean separation between instance-level properties
and class-level namespace semantics.