22

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.

rz.
  • 18,901
  • 10
  • 51
  • 47
  • @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 Mar 11 '13 at 17:24
  • @martineau - Nobody else seemed to believe that either, so the close vote has expired a long time ago. – Bo Persson Mar 11 '13 at 17:28
  • @BoPersson: I stand corrected, technically the info in the [added-on answer](http://stackoverflow.com/a/1355444/355230) to that question does address the general question. – martineau Mar 11 '13 at 18:11
  • Is it important that other instances of this class do **not** have this property? – ideasman42 Jun 12 '16 at 13:19

2 Answers2

38

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.

user2357112 supports Monica
  • 215,440
  • 22
  • 321
  • 400
Alex Martelli
  • 762,786
  • 156
  • 1,160
  • 1,345
  • 12
    I just followed a link to this answer and found it very useful. However, perhaps due to changes in Python over the last few years, A few things didn't work out of the box for me. I had to change `cls.hasattr('__perinstance')` to `hasattr(cls, '__perinstance')`, and I had to add a line within the `if` block: `inst.__class__ = cls`. I hope this helps anyone else who sees this answer to get it working for them! – Blckknght Sep 13 '12 at 01:20
  • 8
    @Blckknght: I don't see how the original answer could have worked because it doesn't ever change the class of the instance to the newly created "per instance" one when that occurs. – martineau Mar 11 '13 at 16:40
3

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.

ideasman42
  • 30,245
  • 24
  • 136
  • 249
  • -1, this does not just affect the behaviour of `my_instance`, it also sets `my_prop` for all other instances of class `type(my_instance)` – ali_m Jul 09 '13 at 11:54
  • @ali_m, Right, thats the purpose, its adding the attribute to the class, The answer above (which is checked as being the correct one). is also adding the property to the class (not the instance). – ideasman42 Jul 09 '13 at 13:57
  • 1
    No, what happens is that `cls = type(cls.__name__, (cls,), {})` creates a new 'per-instance' class whose base is the type of the instance that we're adding the attribute to. When you then assign `inst.__class__ = cls` as in Blckknght's comment, this results in the attribute only being applied to `inst`, not to all instances of class `type(inst)`. – ali_m Jul 09 '13 at 14:17
  • 1
    The answer isn't assigning the `__class__` (that was suggested in a comment). Whatever the case, the OP isn't explicitly asking how to change add a property to a **specific class instance**, if that were the case... my answer would be incorrect. – ideasman42 Jun 12 '16 at 13:18