I'm trying to create a class whose instances can have an arbitrary number of attributes. This can be easily achieved using **kwargs in the init method. Then, for each of its attributes I need to count the access throughout the entire lifecycle of an instance.
If I had a property for each attributes, then I could increment a counter in both getter and setter methods for that attribute accessed at a given time.
The problem is that I need to create those properties dynamically, because I don't know which attributes an instance of my class will have.
I'm sure this can be solved by mean of metaclasses. Although, I'm trying to address that without them.
I've written this code so far:
class CountAttr:
def __init__(self, **kwargs):
# here I init the counter for each keyword argument
self.__count = {attr: 0 for attr in kwargs}
# then I add the attribute to my instance
for kwarg in kwargs:
object.__setattr__(self, '_' + kwarg, kwargs[kwarg])
def getter(self):
self.__count[kwarg] +=1
return object.__getattribute__(self, '_' + kwarg)
def setter(self, value):
self.__count[kwarg] +=1
object.__setattr__(self, '_' + kwarg, kwargs[kwarg], value)
#here comes the issue: the property doesn't work
object.__setattr__(self, kwarg, property(getter, setter))
@property
def count(self):
return self.__count
test = CountAttr(x=5, y=6)
print(test.x, test.y)
Debugging my code, I've assessed that test has attribute _x with value of 5, as well as attribute _y with value of 6. x and y are shown as property objects, so everything seems to work fine. But it doesn't at the end.
Indeed, the final print statement will output this:
<property object at 0x7fbb241d4b30> <property object at 0x7fbb241d4b80>
While the output I'm expecting is: 5 6, that is, the value of _x and _y attributes.
All attribute access in an object in Python, regardless of how the attribute is actually stored in memory (or even if it is computed), goes trough two special methods in the class __setattr__ and __getattribute__.
That is it - just put your counter code in those two methods, use a separate name as instance attribute to store the counter data itself, and go on - no need for fiddling with a metaclass. All you need is a base (or mixin) class with this counter code. If you don't want the initial setup of the arguments to be counted, you can just start the count from "-1" for writting, for example.
class CounterBase:
def __init__(self, **kwargs):
self._counters = {}
for name, value in kwargs.items():
setattr(self, name, value)
super().__init__()
def __getattribute__(self, name):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__setattr__(name, value)
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