22
votes

I know that I can dynamically add an instance method to an object by doing something like:

import types
def my_method(self):
    # logic of method
# ...
# instance is some instance of some class
instance.my_method = types.MethodType(my_method, instance)

Later on I can call instance.my_method() and self will be bound correctly and everything works.

Now, my question: how to do the exact same thing to obtain the behavior that decorating the new method with @property would give?

I would guess something like:

instance.my_method = types.MethodType(my_method, instance)
instance.my_method = property(instance.my_method)

But, doing that instance.my_method returns a property object.

2
@Bo Persson: Although the title of the question python: How to add property to a class dynamically? is basically the same, I don't believe that its accepted answer (including add-on to it) addresses the question generally. - martineau
@martineau - Nobody else seemed to believe that either, so the close vote has expired a long time ago. - Bo Persson
@BoPersson: I stand corrected, technically the info in the added-on answer to that question does address the general question. - martineau
Is it important that other instances of this class do not have this property? - ideasman42

2 Answers

38
votes

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.

3
votes

Since this question isn't asking about only adding to a spesific instance, the following method can be used to add a property to the class, this will expose the properties to all instances of the class YMMV.

cls = type(my_instance)
cls.my_prop = property(lambda self: "hello world")
print(my_instance.my_prop)
# >>> hello world

Note: Adding another answer because I think @Alex Martelli, while correct, is achieving the desired result by creating a new class that holds the property, this answer is intended to be more direct/straightforward without abstracting whats going on into its own method.