Explain Codes LogoExplain Codes Logo

How to add property to a class dynamically?

python
metaprogramming
descriptors
properties
Alex KataevbyAlex Kataev·Feb 14, 2025
TLDR

You can append a new property to a Python class swiftly with setattr(). Here's an example:

class Demo: pass # Add new property 'data' to the class 'Demo' like a bawse. The value - 42. Ya know, the answer to life, universe and everything. setattr(Demo, 'data', 42) # Shout it out to the world! print(Demo().data) # Outputs: 42

Interesting side-note: You're dynamically tweaking the Demo class with its very own data key. Quite meta, isn't it?

Add properties dynamically with Descriptors

All about Descriptors

Descriptors are the beating heart of Python's property, method, and classmethod types. They regulate the ways of accessing attributes giving you the never-seen-before control. Just like driving a sports car, but don't overuse it, we don't want a pileup on the Superhighway of Code. Descriptors can implement __get__, __set__, or __delete__ methods. They're like the bouncers of the Python club, deciding who gets to stay and who gets booted.

class ReadOnly: def __get__(self, instance, owner): return "This is read-only" class MyClass: readOnly = ReadOnly() obj = MyClass() print(obj.readOnly) # Outputs: This is read-only

Properties and Descriptors: Partners in code

Talk about simplicity, the property() function is a built-in descriptor offering an easier way to add custom-ruled attributes. It's like hiring an assistant to manage your chores. Not bad for a day in programming!

class MyClass: def __init__(self): self._my_prop = 0 @property def my_prop(self): return self._my_prop @my_prop.setter def my_prop(self, value): self._my_prop = value obj = MyClass() obj.my_prop = 42 # Setter in action print(obj.my_prop) # Outputs: 42, Getter in action

Metaprogramming with descriptors: The next level

Through the looking glass of metaprogramming, Python classes can defy the odds by having dynamic property access. Just remember to keep it subtle or somebody might pull the Matrix on you!

def create_property(name): storage_name = '_' + name @property def prop(self): return getattr(self, storage_name) @prop.setter def prop(self, value): setattr(self, storage_name, value) return prop class MyClass: pass # With a flick of the wand, a new property comes into existence. new_property = create_property('dynamic_prop') setattr(MyClass, 'dynamic_prop', new_property) obj = MyClass() obj.dynamic_prop = 'Hello' print(obj.dynamic_prop) # Outputs: Hello

Handling dynamic variables

The power of getattr

With the __getattr__ method, an attribute's access behavior can be defined when it's not found in an object’s dictionary. It’s akin to delivering a parcel to a secondary address when the recipient is not at home.

class LazyDB: def __getattr__(self, name): value = 'Value for ' + name setattr(self, name, value) return value db = LazyDB() print(db.some_prop) # Outputs: Value for some_prop

Immutable classes

To fake the immutability of a db resultset, you can rewrite the __setattr__ function to reject alterations post-initialization. Trying to change this class is like trying to rewrite history, good luck with that!

class ImmutableClass: def __init__(self, **properties): super().__setattr__('_is_initialized', False) for key, value in properties.items(): setattr(self, key, value) super().__setattr__('_is_initialized', True) def __setattr__(self, key, value): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"{key} is read-only") super().__setattr__(key, value) immutable = ImmutableClass(property_one='Value') immutable.property_one = 'New Value' # Raises AttributeError

Monkey Patching

Monkey Patching is not about troubleshooting systems in a jungle. It's modifying a module or class at run-time, it's like changing tires on a moving vehicle!

class SomeClass: pass def new_method(self): return 'Monkey Patched Method' SomeClass.new_method = new_method print(SomeClass().new_method()) # Outputs: Monkey Patched Method