Using properties defined at a per-instance level not per-class
-
06-07-2019 - |
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
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