How to add property to a class dynamically?
I suppose I should expand this answer, now that I'm older and wiser and know what's going on. Better late than never.
You can add a property to a class dynamically. But that's the catch: you have to add it to the class.
>>> class Foo(object):
... pass
...
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4
A property
is actually a simple implementation of a thing called a descriptor. It's an object that provides custom handling for a given attribute, on a given class. Kinda like a way to factor a huge if
tree out of __getattribute__
.
When I ask for foo.b
in the example above, Python sees that the b
defined on the class implements the descriptor protocol—which just means it's an object with a __get__
, __set__
, or __delete__
method. The descriptor claims responsibility for handling that attribute, so Python calls Foo.b.__get__(foo, Foo)
, and the return value is passed back to you as the value of the attribute. In the case of property
, each of these methods just calls the fget
, fset
, or fdel
you passed to the property
constructor.
Descriptors are really Python's way of exposing the plumbing of its entire OO implementation. In fact, there's another type of descriptor even more common than property
.
>>> class Foo(object):
... def bar(self):
... pass
...
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>
The humble method is just another kind of descriptor. Its __get__
tacks on the calling instance as the first argument; in effect, it does this:
def __get__(self, instance, owner):
return functools.partial(self.function, instance)
Anyway, I suspect this is why descriptors only work on classes: they're a formalization of the stuff that powers classes in the first place. They're even the exception to the rule: you can obviously assign descriptors to a class, and classes are themselves instances of type
! In fact, trying to read Foo.bar
still calls property.__get__
; it's just idiomatic for descriptors to return themselves when accessed as class attributes.
I think it's pretty cool that virtually all of Python's OO system can be expressed in Python. :)
Oh, and I wrote a wordy blog post about descriptors a while back if you're interested.
Dynamically adding @property in python
The property
descriptor objects needs to live in the class, not in the instance, to have the effect you desire. If you don't want to alter the existing class in order to avoid altering the behavior of other instances, you'll need to make a "per-instance class", e.g.:
def addprop(inst, name, method):
cls = type(inst)
if not hasattr(cls, '__perinstance'):
cls = type(cls.__name__, (cls,), {})
cls.__perinstance = True
inst.__class__ = cls
setattr(cls, name, property(method))
I'm marking these special "per-instance" classes with an attribute to avoid needlessly making multiple ones if you're doing several addprop
calls on the same instance.
Note that, like for other uses of property
, you need the class in play to be new-style (typically obtained by inheriting directly or indirectly from object
), not the ancient legacy style (dropped in Python 3) that's assigned by default to a class without bases.
Pythonic way to dynamically add properties and setters
If you want to do something that looks like property access, but under the performs a function, you want a descriptor. A descriptor is an object that looks like a property but intercepts get and set calls and lets you run whatever code you need. Here's a stackoverflow question with an example that uses descriptors to change an object's translation using descriptors:
https://stackoverflow.com/a/22292961/1936075
You could use the same trick to create, get and set custom attributes in a more readable, less cmds-heavy fashion.
More code here.
How do I dynamically create properties in Python?
Don't use properties but implement the following methods:
__getattr__(self, name)
__setattr__(self, name, value)
__delattr__(self, name)
See http://docs.python.org/reference/datamodel.html#customizing-attribute-access
Your __getattr__
method could look like this:
def __getattr__(self, name):
try:
return self.__dict[name]
except KeyError:
msg = "'{0}' object has no attribute '{1}'"
raise AttributeError(msg.format(type(self).__name__, name))
Dynamically add properties to instances in Python
You can actually do this easily by just changing the name of your function:
>>> class Wrapper(object):
... def __init__(self, data):
... self.data = data
... def __getattr__(self, attr):
... return [d[attr] for d in self.data]
...
>>> Wrapper([{'x': 23}, {'x': 42}, {'x': 5}]).x
[23, 42, 5]
The __getattr__()
special method is called whenever you request an attribute that doesn't exist. The only potential issue here is that you could override this by assigning an attribute. If you need to avoid that, simply override __setattr__()
as well to stop that from happening.
Dynamically adding a property to a class
I don't know why you use operator.methodcaller
here.
When you call f.spam=2
, it will invoke setter.setter = operator.methodcaller('set_value', name)
means setter(r) = r.set_value(name)
. Make no sense in your case.
I suggest you write this way, using @classmethod
:
class Foo(object):
@classmethod
def get_value(self, name):
# read and return value from database
return -1
@classmethod
def set_value(self, name, value):
# store value in database
pass
def add_metadata_property(name):
setattr(Foo, name, property(Foo.get_value, Foo.set_value))
add_metadata_property('spam')
f = Foo()
f.spam # works!
f.spam = 2
If this helped you, please confirm it as the answer. Thanks!
Related Topics
Sqlalchemy Unique Across Multiple Columns
How to Display a 3D Plot of a 3D Array Isosurface in Matplotlib Mplot3D or Similar
Groupby Column and Filter Rows with Maximum Value in Pyspark
Login to Website Using Urllib2 - Python 2.7
How to Initialize the Base (Super) Class
Pandas: Change Data Type of Series to String
Using Psycopg2 with Lambda to Update Redshift (Python)
How to Annotate Types of Multiple Return Values
How to Fix Character Constantly Accelerating in Both Directions After Deceleration Pygame
Efficient Way to Remove Keys with Empty Strings from a Dict
Multithreaded Web Server in Python
Parse Key Value Pairs in a Text File
Pygame Window Not Responding After Few Seconds
Which Version of Python Do I Have Installed
How to Check If There Exists a Process with a Given Pid in Python