views:

113

answers:

4

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
A: 

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
Jonathan Feinberg
This example shows the property being created when the class is declared. However, in my case the variable that I want to be a property isn't "given" to the class (and instance) when it is declared, but rather it is given when the __init__ function runs. However, it is a different class/instance that sets the variable for that instance.
Darth
@Darth: Please do not provide more information by commenting on an answer. Please provide more information by updating your question with more information.
S.Lott
A: 

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
Anurag Uniyal
This is very close but not quite what I wanted. The callback cannot be passed in when the instance is created. In my example, it is possible to get the callback from the "WidthVariable" class, however, I would rather that the class automatically added the variable instead of me having to add it in.
Darth
so instead of passing callback you can set callback to WidthVariable, what I am note sure how, what width variable will decide per instance, anyway see changed code
Anurag Uniyal
A: 

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.

Jonathan Feinberg
+3  A: 

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!-)

Alex Martelli
Thank you! It worked! Although there is the strange issue of it passing two variables (WidthVariable-self and the *<__main__.GottaBeMe object at 0x893e8cc>* to *WidthVariable.get_width*. I do suppose this is going to be unavoidable as when you call the width as say "inst.width", it passes the "inst" as a var to the function.Understanding the solution took some time and is going to take some more (but that is just me learning more python). Also, thanks for letting me know the difference between new-style and old-style classes (I always wondered but never found out)!
Darth
@Darth, if you don't want the WidthVariable self, use `property(self.get_width.im_func)`. No way to avoid the GottaBeMe instance argument, that's always going to be passed because that's how `property` is defined (write your own descriptor if you need a behavior that's different from `property`'s).
Alex Martelli
I asked for a Python wizard, and I got one!
Jonathan Feinberg