Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Cache values of descriptors

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!

like image 630
Anton Melnikov Avatar asked Nov 17 '25 19:11

Anton Melnikov


1 Answers

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)]
like image 56
Ashwini Chaudhary Avatar answered Nov 19 '25 09:11

Ashwini Chaudhary