Question

What I am trying to achieve is something like this:

class object:
    def __init__(self):
        WidthVariable(self)

        print self.width
        #Imagine I did this 60frames/1second later
        print self.width

#output:
>>0
>>25

What I want happening (as above): When WidthVariable - a class - is created it adds the variable width to the object instance. This variable acts like a normal property, but in this particular case it is read-only (only the fget variable is set). Additionally, the fget calls a function defined in WidthVariable which decides what width to return.

However, I have no idea how to do this! I tried it using normal properties but I found that they only work on classes and not per instance - please note that the code I use should be similar as possible to the above (i.e. only code within the __init__ of WidthVariable should set the width variable, nowhere else). Also, self.width cannot be function, because I do not what to call it like self.width(), I want self.width (because it keeps with the rest of the design I have).

To clarify, the complete code would be something like this:

class MyObject:
    def __init__(self)
        WidthVariable(self)
        print self.width

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

    def get_width(self):
        value = #do_stuff
        return value #The Value

#output:
>>25 #Whatever the Value was
Was it helpful?

Solution

Since, as @Jonathan says, descriptors (including properties) are per-class, not per-instance, the only way to get different per-instance descriptors is to have each instance individualize its own class. That's pretty shallow and easy as far as metaprogramming goes;-)...:

class Individualistic(object_or_whatever_bases):
  def __init__(self, whatever_args):
    self.__class__ = type('GottaBeMe', (self.__class__, object), {})
    # keep rocking...!-)

I'm also adding object explicitly because it's needed (in Python 2.*, and you do say that's what you're using!!!) to make classes new-type. Never use legacy classes any more, they don't behave correctly with respect to properties and much else besides (and for backwards compatibility they can't -- in Python 3, legacy classes have finally been annihilated so EVERY class is new-style without the requirement to explicitly inherit from object any more!).

Now, any descriptor that's placed in self.__class__.__dict__ will only affect this one instance, none other. There's a bit of overhead (each GottaBeMe class and therefore each instance has its own __dict__, etc), but nothing too terrible.

Now, all that's needed to satisfy the original request is to change:

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

(also renaming the object arg sensibly to avoid trampling on a built-in, and making the class new-style because you should ALWAYS make EVERY class new-style;-), to:

class WidthVariable(object):
    def __init__(self, obj)
        obj.__class__.width = property(self.get_width)

Nothing too black-magicky, as you can see!-)

OTHER TIPS

I don't understand the way you've constructed your example, nor do I understand what you mean about "normal properties" only working "on classes". Here's how you create a read-only property:

class Foo(object):
    # create read-only property "rop"
    rop = property(lambda self: self._x)

    def __init__(self):
        self._x = 0

    def tick(self):
        self._x += 1    

f = Foo()
print f.rop # prints 0
f.tick()
f.tick()
print f.rop # prints 2
f.rop = 4 # this will raise AtributeError

I believe I now understand your question, and I also believe you're out of luck.

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

Descriptors (which are used to implement properties) must appear in the class's __dict__, and cannot appear in the instance's __dict__. In other words, Python is not Ruby!

I happily await correction from a godly Python metaprogrammer, but I think I'm right.

Not very clear what you want? but I assume you want obj.width to be customized for each instance Here is a easy way by using plain properties, width property returns value returned by a per instance callback

class MyClass(object):
    def __init__(self, callback):
        self.callback = callback

    def get_width(self):
        return self.callback()

    width = property(get_width)

def w1(): return 0
def w2(): return 25

o1 = MyClass(w1)
o2 = MyClass(w2)

print o1.width
print o2.width

If callback can not be passed we can assign it to WidthVariable which returns width based on instance

class MyClass(object):
    def __init__(self):
        self.callback = WidthVariable(self)

    def get_width(self):
        return self.callback()

    width = property(get_width)

class WidthVariable(object):
    def __init__(self, obj):
        self.obj = obj

    def __call__(self):
        return hash(self.obj)

o1 = MyClass()
o2 = MyClass()

print o1.width
print o2.width
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top