使用 __setattr__ 和 __getattr__ 与 __slots__ 进行委托而不触发无限递归
Using __setattr__ and __getattr__ for delegation with __slots__ without triggering infinite recursion
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'
Python __slots__
只是 auto-generated 描述符的糖。调用描述符在 object
的 __setattr__
和 __getattr__
(或 __*attribute__
,我没有深入挖掘)中实现。最重要的是,我们覆盖了默认值 __setattr__
,因此无法在构造函数中使用点符号来初始化值。由于 slotted 变量的值尚未初始化,我们的 __setattr__
导致访问 __getattr__
(本身是不正确的行为!),并且 __getattr__
需要 slotted 变量本身,所以 - 无限递归。
对于非__slots__
classes,它可以使用__dict__
解决。我们不能使用 __dict__
因为我们在 __slots__
classes.
中没有它们
文档说 __slots__
是作为描述符实现的。描述符是具有魔法方法的特殊对象,设置为 class 的方式与设置静态方法和道具的方式相同(顺便说一句 classmethod
和 staticmethod
也构造描述符),通常不作用于对象本身,但在其父 class.
因此,要正确初始化值,我们应该显式调用描述符方法
class BCorrect:
__slots__ = ("b",)
def __init__(self, b) -> None:
self.__class__.b.__set__(self, b)
def __getattr__(self, k):
return getattr(self.b, k)
def __setattr__(self, k, v):
setattr(self.b, k, v)
然后一切正常:
b = BCorrect(a)
print(b.a) # 1
b.a = 2
print(a.a) # 2
更合适的方法是在委派之前检查属性名称是否在 class 层次结构中任何可用的 __slots__
中:
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)
这样做的好处是它不会破坏继承并且在 __init__
中不需要任何魔法。
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'
Python __slots__
只是 auto-generated 描述符的糖。调用描述符在 object
的 __setattr__
和 __getattr__
(或 __*attribute__
,我没有深入挖掘)中实现。最重要的是,我们覆盖了默认值 __setattr__
,因此无法在构造函数中使用点符号来初始化值。由于 slotted 变量的值尚未初始化,我们的 __setattr__
导致访问 __getattr__
(本身是不正确的行为!),并且 __getattr__
需要 slotted 变量本身,所以 - 无限递归。
对于非__slots__
classes,它可以使用__dict__
解决。我们不能使用 __dict__
因为我们在 __slots__
classes.
文档说 __slots__
是作为描述符实现的。描述符是具有魔法方法的特殊对象,设置为 class 的方式与设置静态方法和道具的方式相同(顺便说一句 classmethod
和 staticmethod
也构造描述符),通常不作用于对象本身,但在其父 class.
因此,要正确初始化值,我们应该显式调用描述符方法
class BCorrect:
__slots__ = ("b",)
def __init__(self, b) -> None:
self.__class__.b.__set__(self, b)
def __getattr__(self, k):
return getattr(self.b, k)
def __setattr__(self, k, v):
setattr(self.b, k, v)
然后一切正常:
b = BCorrect(a)
print(b.a) # 1
b.a = 2
print(a.a) # 2
更合适的方法是在委派之前检查属性名称是否在 class 层次结构中任何可用的 __slots__
中:
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)
这样做的好处是它不会破坏继承并且在 __init__
中不需要任何魔法。