I would like to clarify some things about Python's descriptors. I want to add a property to my class with some complex set/get mechanics and cache those calculated values inside the descriptor object. A simplified example looks like this:
class Pro(object):
"""My descriptor class"""
def __init__(self):
self.value = None
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = value
class Something(object):
"""My simple class"""
pro = Pro()
a = Something()
a.pro = 1
b = Something()
b.pro = 2
print(a.pro, b.pro) # At first, I've expected them to be 1 and 2
I thought somehow that pro attribute would be unique instance of Pro for every instance of Something, obviously I was wrong. Looks like I should use something like instance._value instead of self.value inside __set__ and __get__, but I've really hoped to hide everything inside Pro class. Is this even possible? Thanks!
The problem with your code is that you're setting the attribute on Pro's instance which is going to shared by all instances of Something. To fix this you should set an attribute on the individual instance of Something, one way to do that is to use a metaclass:
class Meta(type):
def __new__(cls, name, bases, dct):
for k, v in dct.items():
if isinstance(v, Pro):
# add an _ in front of the name
v.name = '_' + k
return super(Meta, cls).__new__(cls, name, bases, dct)
class Pro(object):
def __get__(self, ins, typ):
return getattr(ins, self.name)
def __set__(self, ins, val):
setattr(ins, self.name, val)
class Something(object):
"""My simple class"""
__metaclass__ = Meta
pro = Pro()
a = Something()
a.pro = 1
b = Something()
b.pro = 2
Demo:
>>> a.pro, b.pro
(1, 2)
>>> a.__dict__
{'_pro': 1}
>>> b.__dict__
{'_pro': 2}
>>> a.pro = 100
>>> a.__dict__
{'_pro': 100}
So there is no way around making hidden attributes in Something instances, right?
No, there is. You can store a dictionary in Pro's instance that stores all the values related to each instance of Something. For example if Something's instances are hashable then you can do something like this using weakref.WeakKeyDictionary. The WeakKeyDictionary will make sure that that once a Something's instance has no references left then it is garbage collected immediately which is not possible with a normal dict:
from weakref import WeakKeyDictionary
class Pro(object):
def __init__(self):
self.instances = WeakKeyDictionary()
def __get__(self, ins, typ):
return self.instances[ins]
def __set__(self, ins, val):
self.instances[ins] = val
p = Pro()
class Something(object):
"""My simple class"""
pro = p
a = Something()
a.pro = 1
b = Something()
b.pro = 2
print a.pro, b.pro
print p.instances.items()
del a
print p.instances.items()
Output:
1 2
[(<__main__.Something object at 0x7fb80d0d5310>, 1), (<__main__.Something object at 0x7fb80d0d5350>, 2)]
[(<__main__.Something object at 0x7fb80d0d5350>, 2)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With