TypeError: descriptor __get__ for object objects doesnt apply to a type object in Python
Handle instance is None in custom descriptor __get__(self, instance, owner) methods to support class-level attribute access.
Root Cause Analysis
This error occurs when Python accesses a custom descriptor (or low-level C-extension property) from a class object directly (e.g. MyClass.my_descriptor), and the descriptor's __get__ implementation strictly expects an instance of the class rather than a type object.
1. The Python Descriptor Protocol Contract
In Python, descriptors implement __get__(self, instance, owner). When accessed on an instance (obj.my_descriptor), instance is the object instance. When accessed on the class (MyClass.my_descriptor), instance is None and owner is the class object (type). If __get__ unconditionally accesses instance.attribute without checking for None, Python raises a TypeError or AttributeError.
2. Custom Validation Descriptors and ORM Fields
Writing custom field descriptors (similar to SQLAlchemy Column or Django Field) that assume an instance always exists.
3. Calling Bound Methods Directly on the Class Without an Instance
Passing the class itself to an unbound descriptor expecting a specific instance type.
4. Mixing @classmethod with @property in Python 3.11+
In Python 3.11+, chaining @classmethod @property was deprecated and removed in Python 3.13 due to descriptor protocol ambiguities.
Reproduction Code (MCVE)
# Simulating a custom descriptor that fails on class-level access
class StrictFieldDescriptor:
def __get__(self, instance, owner):
if instance is None:
raise TypeError("descriptor '__get__' for 'object' objects doesn't apply to a 'type' object")
return instance._value
class DataRecord:
field = StrictFieldDescriptor()
# Accessing on class object raises TypeError
DataRecord.field
Solution 1: Return the Descriptor Itself When instance is None
Follow standard Python descriptor protocol: return self when instance is None so class inspection, docstrings, and ORM schemas work.
class RobustDescriptor:
def __init__(self, name=None):
self.name = name
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self # Return descriptor on class access
return instance.__dict__.get(self.name, None)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
class Person:
age = RobustDescriptor()
# Class access returns descriptor object
print(f'Class access: {Person.age}')
p = Person()
p.age = 30
print(f'Instance access: {p.age}')
Solution 2: Use classmethod for Class-Level Read-Only Access
If a property must compute a value at the class level, use a @classmethod instead of chaining @property.
class Config:
_version = '2.4.0'
@classmethod
def get_version(cls):
return cls._version
print(f'Class version: {Config.get_version()}')
A common mistake is using getattr(MyClass, 'field') and expecting the raw descriptor without triggering __get__. To get the raw descriptor without invocation, inspect MyClass.__dict__['field'] or use inspect.getattr_static(MyClass, 'field'). Edge cases occur with __set_name__ in Python 3.6+: descriptors can automatically record their assigned attribute name on the owner class. Contrast this error with TypeError: 'property' object is not callable, which occurs when trying to invoke a property with parentheses: obj.name().