CodingLambdas CodingLambdas - 7 months ago 8
Python Question

How to let the python descriptor know that an instance of the owning class was deleted

I am currently building a little library, and I ran across a problem with descriptors: I created a python descriptor and it has to store values for seperate classes, but I don't want to use the class as storage. And I don't want the user having to inherit anything for those descriptors to work.

But when an instance of the object is deleted, I want to delete its data in the descriptor for that instance. (The instance can be deleted, because the descriptor doesn't hold a reference to it, I index those with their id's in a dictionary)

And this has to be done, because another instance can be created, with the same id, resulting in a 'data transfer' from the old object to the new, and that is not helpful in any way.

Is there a way to let the descriptor know that an instance of the class the descriptor is part of was deleted?

(

__delete__
just fires if the attribute is deleted, not if the instance is getting removed)

Here's a little bit of code to show you what this is all about:

class CounterDescriptor(object): # I need the answer for something different, but the problem is the same. My code is just much larger and a whole lot more complicated.
def __init__(self) -> None:
self.counts = {}

def __get__(self, instance: object, owner: type) -> object:
instance_id = id(instance)
if instance_id not in self.counts:
self.counts[instance_id] = 0
self.counts[instance_id] += 1
return self.counts[instance_id]

def __set__(self, instance: object, value: int) -> None:
self.counts[id(instance)] = int(value)


class SomethingUsingTheCounterDescriptor(object):
x = CounterDescriptor()

x = SomethingUsingTheCounterDescriptor()
x.x # \-> count goes one higher (-> now it is 1)
del x # Now I want to get rid of the data stored for x in the CounterDescriptor.


I'll just thank you in advance,

CodenameLambda

Answer

Another version with weak references that does not need hashable instances:

from weakref import ref


class CounterDescriptor(object): 

    def __init__(self) -> None:
        self.counts = {}
        self.refs = {}

    def _clean_up(self):
        for id_, ref in self.refs.items():
            if ref() is None:
                del self.counts[id_]

    def __get__(self, instance: object, owner: type) -> object:
        self._clean_up()
        inst_id = id(instance)
        if instance is None:
            return self.counts
        if inst_id not in self.counts:
            self.counts[inst_id] = 0
            self.refs[inst_id] = ref(instance)
        self.counts[inst_id] += 1
        return self.counts[inst_id]

    def __set__(self, instance: object, value: int) -> None:
        self._clean_up()
        inst_id = id(instance)
        self.counts[inst_id] = int(value)
        self.refs[inst_id] = ref(instance)

class SomethingUsingTheCounterDescriptor(object):
    x = CounterDescriptor()
s = SomethingUsingTheCounterDescriptor()
s.x
s.x
print(SomethingUsingTheCounterDescriptor.x)
del s
print(SomethingUsingTheCounterDescriptor.x)

Output:

{4460071120: 2}
{}