class A:
__slots__ = ("a",)
def __init__(self) -> None:
self.a = 1
class B1:
__slots__ = ("b",)
def __init__(self, b) -> None:
self.b = b
def __getattr__(self, k):
return getattr(self.b, k)
def __setattr__(self, k, v):
setattr(self.b, k, v)
class B2:
__slots__ = ("b",)
def __init__(self, b) -> None:
self.b = b
def __getattr__(self, k):
return getattr(super().__getattr__("b"), k)
def __setattr__(self, k, v):
setattr(super().__getattr__("b"), k, v)
class B3:
__slots__ = ("b",)
def __init__(self, b) -> None:
self.b = b
def __getattr__(self, k):
return getattr(getattr(super(), "b"), k)
def __setattr__(self, k, v):
setattr(getattr(super(), "b"), k, v)
a = A()
b = B1(a)
print(b.a) # RecursionError: maximum recursion depth exceeded
b = B2(a)
print(b.a) # AttributeError: 'super' object has no attribute '__getattr__'
b = B3(a)
print(b.a) # AttributeError: 'super' object has no attribute 'b'
A more proper way is to check if the attribute name is in any of the available __slots__
up the class hierarchy before delegating:
class BCorrect(object):
__slots__ = ('b',)
def __init__(self, b) -> None:
self.b = b
def _in_slots(self, attr) -> bool:
for cls in type(self).__mro__:
if attr in getattr(cls, '__slots__', []):
return True
return False
def __getattr__(self, attr):
if self._in_slots(attr):
return object.__getattr__(self, attr)
return getattr(self.b, attr)
def __setattr__(self, attr, value):
if self._in_slots(attr):
object.__setattr__(self, attr, value)
return
setattr(self.b, attr, value)
This has the advantages that it does not break inheritance and does not need any magic in __init__
.