Explain Codes LogoExplain Codes Logo

Using property() on classmethods

python
class-properties
metaclass
descriptor
Nikita BarsukovbyNikita Barsukov·Jan 28, 2025
TLDR

Combine the @property decorator with @classmethod to create class-level properties in Python 3.9 and upwards. Here's a short, snappy example:

class MyClass: _my_class_value = 0 @classmethod @property def my_value(cls): return cls._my_class_value @my_value.setter @classmethod def my_value(cls, value): cls._my_class_value = value # Usage MyClass.my_value = 10 # Who says class can't have properties? ;) print(MyClass.my_value) # Get the class property, not your everyday property!

This code generates a class-level attribute named my_value. Get and set its value as if it were an instance property, but right at the class level.

Creating sophisticate class-level properties

Positioning classmethods in metaclass

Increase code readability and structure by grouping classmethods inside a metaclass. Especially handy when dealing with class hierarchies that resemble a season finale reunion episode.

class Meta(type): @property def my_value(cls): return cls._my_class_value @my_value.setter def my_value(cls, value): cls._my_class_value = value class MyClass(metaclass=Meta): _my_class_value = 0 # Usage MyClass.my_value = 20 # Here, take this 'value'. Wait, where are you going?! To the metaclass?! print(MyClass.my_value) # Hello from the metaclass side...

Getting fancy with custom descriptors

If you want to customize how class properties act, roll out a custom descriptor with __get__, __set__, and __delete__.

class ClassPropertyDescriptor: def __init__(self, get=None, set=None, delete=None): self.getter = get self.setter = set self.deleter = delete def __get__(self, obj, type=None): if self.getter is None: raise AttributeError("access denied, it's a no-get zone") return self.getter() def __set__(self, obj, value): if self.setter is None: raise AttributeError("Nice try but you can't set this!") self.setter(value) def __delete__(self, obj): if self.deleter is None: raise AttributeError("Not so fast, you can't delete this!") self.deleter()

Minting read-only class properties

A @classproperty decorator can make you a read-only class property, mixing @classmethod and @property. It'll just sit there, you can look but you can't touch.

class classproperty(object): def __init__(self, func): self.func = func def __get__(self, instance, owner): return self.func(owner) class MyClass: _my_class_value = 42 @classproperty def my_value(cls): return cls._my_class_value # Usage print(MyClass.my_value) # It's 42. The answer to life, the universe, and everything!

Traps and tips

Prioritizing descriptor decorators

Decorator order is crucial; @classmethod must preceed @property for proper descriptor communication. If flipped, an "unexpected item in the bagging area" kind of error might surprise you.

Python's version game plays you

Python 3.8 to 3.10 are cool with creating properties on a metaclass, but Python 3.11 delivers a "NOPE". Always validate with the updated Python docs.

Property underclassman becomes overachiever

To wrap up repetitive patterns, subclassproperty and create decorators like @classproperty. Not only does this offer more clarity and reduce redundancy, it also packages behavior nicely for reuse.

class classproperty(property): def __get__(self, cls, owner): return classmethod(self.fget).__get__(None, owner)() class MyClass: _my_class_value = 'Python' @classproperty def my_value(self): return self._my_class_value # Usage print(MyClass.my_value) # Get the class property, prints 'Python'

Metaclass does the Inception!

While defining properties with a metaclass, it also initializes static variable values. Talk about having control issues!

Extras and niceties

Don't stare, compare!

Before saying "I do" to a property and @classmethod implementation, test variations and assess them based on simplicity, readability, and maintainability.

Shield the internals with metaclass syntax

When a class screams of messy internals, declare metaclasses outside the class and assign them with metaclass=... syntax. Cleaner class bodies, and metaclass roles are highlighted.

Dealing with instance methods

With classproperty, you can make properties work both on the class and at instance level. Code stays uniform, recursive and 'Pythonic'.