Search code examples
pythonpython-descriptors

Accessing attributes of a Python Descriptor


Not sure if this is feasible or not. The implementation/example below is dummy, FYI.

I have a Python class, Person. Each person has a public first name and a public last name attribute with corresponding private attributes - I'm using a descriptor pattern to manage access to the underlying private attributes.

I am using the descriptor to count the number of times the attribute is accessed as well as obtain the underlying result.

class AttributeAccessCounter:
    def __init__(self):
        self._access_count = 0

    def __get__(self, instance, owner):
        self._access_count += 1
        return getattr(instance, self.attrib_name)

    def __set_name__(self, obj, name):
        self.attrib_name = f'_{name}'

    @property
    def counter(self):
        return self._access_count


class Person:
    first = AttributeAccessCounter()
    last = AttributeAccessCounter()

    def __init__(self, first, last):
        self._first = first
        self._last = last

From an instance of the class Person, how can I access the _access_count or property counter?

john = Person('John','Smith')
print(john.first) # 'John'
print(john.first.counter) # AttributeError: 'str' object has no attribute 'counter'

Solution

  • Currently you don't differentiate when the descriptor is accessed through the instance or through the class itself. property does this for example. It gives you the descriptor object when you access it through the class.

    You can do the same:

        def __get__(self, instance, owner):
            if instance is None:
                return self
            self._access_count += 1
            return getattr(instance, self.attrib_name)
    

    Now the counter property of the descriptor class can be accessed.

    However there is another problem pointed out by @user2357112 in the comment. You store this count on descriptor and it's shared between different instances. You can't really tell first attribute of which instance of Person is accessed n times.

    To solve that, if you still want to store it in the descriptor object, one way is to use a dictionary and call e method for getting the count.

    Here is the complete code:

    from collections import defaultdict
    
    
    class AttributeAccessCounter:
        def __init__(self):
            self._access_counts = defaultdict(int)
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            self._access_counts[instance] += 1
            return getattr(instance, self.attrib_name)
    
        def __set_name__(self, obj, name):
            self.attrib_name = f"_{name}"
    
        def count(self, obj):
            return self._access_counts[obj]
    
    
    class Person:
        first = AttributeAccessCounter()
        last = AttributeAccessCounter()
    
        def __init__(self, first, last):
            self._first = first
            self._last = last
    
    
    john = Person("John", "Smith")
    foo = Person("foo", "bar")
    
    print(Person.first.count(john))  # 0
    print(john.first)                # 'John'
    print(Person.first.count(john))  # 1
    print(Person.first.count(foo))   # 0