How to implement __setattr__ without infinite recursion
Posted: Wed Oct 02, 2019 4:45 am
I'm trying to implement a Register class which allows direct access to bitfields on a register. I need it to work identical to attribute access on a uctypes.struct bitfield, but I need to read/write to an I2C device behind the scenes whenever the bitfield attributes are accessed.
i.e. something like the following:
I tried to implement this using composition where attribute access on the bitfield attributes would be delegated to the internal self.struct object:
I can't quite figure out how to implement a __setattr__ for something like this in MicroPython without ending up in an infinite recursion situation.
Here's a very simple example highlighting the issue. Instantiating Test1 leads to infinite recursion because the self._storage assignment calls __setattr__:
In CPython, I think this would normally be addressed using one of the two options below (Test2 or Test3). However, in MicroPython, objects don't support __setattr__ and obj.__dict__ is read-only, which prevents both of these from working.
Is there a way to implement this in MicroPython?
i.e. something like the following:
Code: Select all
r = Register(addr, layout)
r.FIELD1 = 0x2
r.FIELD2 = 0x1
(etc...)
Code: Select all
class Register:
def __init__(self, addr, layout):
self.addr = addr
self._buf = bytearray(sizeof(layout))
self._struct = uctypes.struct(uctypes.addressof(self._buf), layout)
def __getattr__(self, name):
# I2C bus read code goes here
return getattr(self._struct, name)
def __setattr__(self, name, value):
# this obviously doesn't work, but I'm not sure what to put here???
if hasattr(self.struct, name):
setattr(self._struct, name, value)
# I2C bus write code goes here
else:
setattr(self, name, value)
Here's a very simple example highlighting the issue. Instantiating Test1 leads to infinite recursion because the self._storage assignment calls __setattr__:
Code: Select all
class Storage:
def __init__(self):
self.x = 1
self.y = 2
self.z = 3
class Test1:
def __init__(self):
self._storage = Storage()
def __getattr__(self, name):
return getattr(self._storage, name)
def __setattr__(self, name, value):
setattr(self._storage, name, value)
t1 = Test1() # RuntimeError: maximum recursion depth exceeded
Code: Select all
class Test2:
def __init__(self):
object.__setattr__(self, '_storage', Storage()) # avoids infinite recursion
def __getattr__(self, name):
return getattr(self._storage, name)
def __setattr__(self, name, value):
setattr(self._storage, name, value)
t2 = Test2()
t2.x = 10
Code: Select all
class Test3:
def __init__(self):
self.__dict__['_storage'] = Storage() # avoids infinite recursion
def __getattr__(self, name):
return getattr(self._storage, name)
def __setattr__(self, name, value):
setattr(self._storage, name, value)
t3 = Test3()
t2.x = 10