4

I am trying to write a class with dynamic properties. Consider the following class with two read-only properties:

class Monster(object):
    def __init__(self,color,has_fur):
        self._color = color
        self._has_fur = has_fur

    @property
    def color(self): return self._color

    @property
    def has_fur(self): return self._has_fur

I want to generalize this so that __init__ can take an arbitrary dictionary and create read-only properties from each item in the dictionary. I could do that like this:

class Monster2(object):
    def __init__(self,traits):
        self._traits = traits

        for key,value in traits.iteritems():
            setattr(self.__class__,key,property(lambda self,key=key: self._traits[key]))

However, this has a serious drawback: every time I create a new instance of Monster, I am actually modifying the Monster class. Instead of creating properties for my new Monster instance, I am effectively adding properties to all instances of Monster. To see this:

>>> hasattr(Monster2,"height")
False
>>> hasattr(Monster2,"has_claws")
False
>>> blue_monster = Monster2({"height":4.3,"color":"blue"})
>>> hasattr(Monster2,"height")
True
>>> hasattr(Monster2,"has_claws")
False
>>> red_monster = Monster2({"color":"red","has_claws":True})
>>> hasattr(Monster2,"height")
True
>>> hasattr(Monster2,"has_claws")
True

This of course makes sense, since I explicitly added the properties as class attributes with setattr(self.__class__,key,property(lambda self,key=key: self._traits[key])). What I need here instead are properties that can be added to the instance. (i.e. "instance properties"). Unfortunately, according to everything I have read and tried, properties are always class attributes, not instance attributes. For example, this doesn't work:

class Monster3(object):
    def __init__(self,traits):
        self._traits = traits

        for key,value in traits.iteritems():
            self.__dict__[key] = property(lambda self,key=key: self._traits[key])

>>> green_monster = Monster3({"color":"green"})
>>> green_monster.color
<property object at 0x028FDAB0>

So my question is this: do "instance properties" exist? If not, what is the reason? I have been able to find lots about how properties are used in Python, but precious little about how they are implemented. If "instance properties" don't make sense, I would like to understand why.

3
  • 1
    Is there a reason why you're avoiding using subclasses? Commented Nov 6, 2013 at 21:15
  • 1
    Why would you even want to do this? If all of your getter functions are just identical functions that return a value from a dict, why even have them? The only reason to have getters in the first place is if you need to run some code to generate the values. Commented Nov 6, 2013 at 21:18
  • @abarnert: Properties are also commonly used to create read-only variables, notice the lack of setters. You're right though, this is a pretty trivial example, my real code does have more complex getters. Commented Nov 6, 2013 at 21:42

6 Answers 6

8

No, there is no such thing as per-instance properties; like all descriptors, properties are always looked up on the class. See the descriptor HOWTO for exactly how that works.

You can implement dynamic attributes using a __getattr__ hook instead, which can check for instance attributes dynamically:

class Monster(object):
    def __init__(self, traits):
        self._traits = traits

    def __getattr__(self, name):
        if name in self._traits:
            return self._traits[name]
        raise AttributeError(name)

These attributes are not really dynamic though; you could just set these directly on the instance:

class Monster(object):
    def __init__(self, traits):
        self.__dict__.update(traits)
Sign up to request clarification or add additional context in comments.

Comments

3

So my question is this: do "instance properties" exist?

No.

If not, what is the reason?

Because properties are implemented as descriptors. And the magic of descriptors is that they do different things when found in an object's type's dictionary than when found in the object's dictionary.

I have been able to find lots about how properties are used in Python, but precious little about how they are implemented.

Read the Descriptor HowTo Guide linked above.


So, is there a way you could do this?

Well, yes, if you're willing to rethink the design a little.

For your case, all you want to do is use _traits in place of __dict__, and you're generating useless getter functions dynamically, so you could replace the whole thing with a couple of lines of code, as in Martijn Pieters's answer.

Or, if you want to redirect .foo to ._foo iff foo is in a list (or, better, set) of _traits, that's just as easy:

def __getattr__(self, name):
    if name in self._traits:
        return getattr(self, '_' + name)
    raise AttributeError

But let's say you actually had some kind of use for getter functions—each attribute actually needs some code to generate the value, which you've wrapped up in a function, and stored in _traits. In that case:

def __getattr__(self, name):
    getter = self._traits.get(name)
    if getter:
        return getter()
    raise AttributeError

Comments

1

What I need here instead are properties that can be added to the instance.

A property() is a descriptor and those only work when stored in classes, not when stored in instances.

An easy way to achieve the effect of an instance property is do def __getattr__. That will let you control the behavior for lookups.

Comments

0

In case you don't need to make that properties read-only - you can just update object __dict__ with kwargs

class Monster(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

than you can make instances of that class like that:

m0 = Monster(name='X')
m1 = Monster(name='godzilla', behaviour='godzilla behaviour')

Comments

0

Another way of doing what you want could be to dynamically create monster classes. e.g.

def make_monster_class(traits):
    class DynamicMonster(object):
        pass

    for key, val in traits.items():
        setattr(DynamicMonster, key, property(lambda self, val=val: val))

    return DynamicMonster()

blue_monster = make_monster_class({"height": 4.3, "color": "blue"})
red_monster = make_monster_class({"color": "red", "has_claws": True})
for check in ("height", "color", "has_claws"):
    print "blue", check, getattr(blue_monster, check, "N/A")
    print "red ", check, getattr(red_monster, check, "N/A")

Output:

blue height 4.3
red  height N/A
blue color blue
red  color red
blue has_claws N/A
red  has_claws True

Comments

0

I don't necessarily recommend this (the __getattr__ solution is generally preferable) but you could write your class so that all instances made from it have their own class (well, a subclass of it). This is a quick hacky implementation of that idea:

class MyClass(object):
    def __new__(Class):
        Class = type(Class.__name__, (Class,), {})
        Class.__new__ = object.__new__   # to prevent infinite recursion
        return Class()

m1 = MyClass()
m2 = MyClass()
assert type(m1) is not type(m2)

Now you can set properties on type(self) with aplomb since each instance has its own class object.

@Claudiu's answer is the same kind of thing, just implemented with a function instead of integrated into the instance-making machinery.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.