Noc*_*wer 18 python class slots
最近Python中的某种情况让我感到震惊,经过一番研究后,其原因仍然不完全清楚.以下类定义似乎完美无缺,并将产生预期的内容:
class A: __slots__ = 'a', 'b'
class B(A): __slots__ = ()
class C(A): __slots__ = ()
class D(B, C): __slots__ = ()
Run Code Online (Sandbox Code Playgroud)
这些是以钻石继承模式排列的四个类.但是,不允许有些类似的模式.以下类定义看起来好像它们的功能与第一个相同:
class B: __slots__ = 'a', 'b'
class C: __slots__ = 'a', 'b'
class D(B, C): __slots__ = ()
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
class D(B, C): __slots__ = ()
TypeError: multiple bases have instance lay-out conflict
Run Code Online (Sandbox Code Playgroud)
但是,TypeError
在此示例中引发了a .因此出现了三个问题:(1)考虑到插槽名称,这是Python中的错误吗?(2)这样的答案有什么理由?(3)最好的解决方法是什么?
参考文献:
不能从定义的多个类继承
__slots__
?
关闭。
当存在布局冲突时,您不能从多个定义非空的 类继承__slots__
。
插槽有一个有序的布局,在类中创建的描述符依赖于这些位置,因此它们在多重继承下不能有布局冲突。
您最简单的方法失败了,因为每个a
和b
被认为是不同的插槽,并且布局算法不检查它们在语义上是否相同:
class B: __slots__ = 'a', 'b' # creates descriptors in B for a, b
class C: __slots__ = 'a', 'b' # creates new, different descriptors in C
class D(B, C): __slots__ = () # B.a or C.a comes first?
Run Code Online (Sandbox Code Playgroud)
您的第一个示例有效,因为多重继承仅获取A
的插槽,因此所有情况都使用A
的描述符和位置/布局。例如,将允许以下内容:
class A: __slots__ = 'a', 'b' # shared parent, ok
class B(A): __slots__ = () # B or C must be empty
class C(A): __slots__ = 'c', # Since C is nonempty, B must be empty to inherit from both
class D(B, C): __slots__ = 'd', 'e'
Run Code Online (Sandbox Code Playgroud)
实例化 D,并使用这些插槽:
d = D()
d.a = d.b = d.c = d.d = d.e = 'foo'
Run Code Online (Sandbox Code Playgroud)
我们不能动态创建变量:
>>> d.f = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'f'
Run Code Online (Sandbox Code Playgroud)
以上是解决有问题代码的一种方法,但它可能需要一些重写 - 如果您决定B
需要另一个插槽,则必须将 B 的功能重构为一个抽象,以获得 D 的代码重用(这很好,但是可能令人困惑)。
使用抽象是最佳实践,另一种解决方案是这样做,其中抽象类和/或 mixin 包含具体类的功能:
class AbstractB: __slots__ = ()
class B(AbstractB): __slots__ = 'a', 'b'
class AbstractC: __slots__ = ()
class C(AbstractC): __slots__ = 'a', 'b'
class Mixin: __slots__ = ()
class D(AbstractB, AbstractC, Mixin): __slots__ = 'a', 'b'
Run Code Online (Sandbox Code Playgroud)
您的第一个示例非常可行,因为它避免了布局冲突,这只是使用抽象而不是具体化来重新构想解决方案。
(1) 考虑到插槽名称,这是 Python 中的错误吗?
不,尽管在这件事上有很多混乱,但它还是有一些记录,并且错误试图使这种行为变得清晰。
(2) 什么会证明这样的答案是正确的?
定义槽的类获得的描述符知道它们的数据在位置上的去向。如果布局改变,描述符就会出错。
每个子类都可以创建自己的布局和自己的描述符吗?我想它可以,但这需要对它们的工作方式进行一些改写,并且需要一些政治意愿来做到这一点,并且可能会破坏在 C api 中闲逛并依赖当前行为的其他用户。
(3) 最好的解决方法是什么?
定义“最佳”。
编写速度最快且可能最不复杂?:只需避免像第一个示例中那样的布局冲突。
最佳实践?:使用抽象继承树,并在您的具体化中定义槽。虽然这种方法可能有更多的类,但对于其他人和“未来的你”来说,处理起来可能不那么复杂。
通过强制没有类定义 __slots__ 的约束,可以构造一个具有所有子类所需特征的特殊对象类。该类被注册为常规对象的别名。
class _object: __slots__ = '_MetaSafe__exec', '__dict__'
class MetaSafe(type):
__REGISTRY = {object: _object}
@classmethod
def clone(cls, old):
return cls(old.__name__, old.__bases__, dict(old.__dict__), old)
def __new__(cls, name, bases, classdict, old=None):
# Check on a few classdict keys.
assert '__new__' not in classdict, '__new__ must not be defined!'
assert '__slots__' not in classdict, '__slots__ must not be defined!'
assert '__module__' in classdict, '__module__ must be defined!'
# Validate all the parent classes.
valid = []
for base in bases:
if base in cls.__REGISTRY:
valid.append(cls.__REGISTRY[base])
elif base in cls.__REGISTRY.values():
valid.append(base)
else:
valid.append(cls.clone(base))
# Wrap callables without thread mark.
for key, value in classdict.items():
if callable(value):
classdict[key] = cls.__wrap(value)
# Fix classdict and create new class.
classdict.update({'__new__': cls.__new, '__slots__': (), '__module__':
'{}.{}'.format(__name__, classdict['__module__'])})
cls.__REGISTRY[old] = new = \
super().__new__(cls, name, tuple(valid), classdict)
return new
def __init__(self, name, bases, classdict, old=None):
return super().__init__(name, bases, classdict)
@staticmethod
def __wrap(func):
@functools.wraps(func)
def safe(self, *args, **kwargs):
return self.__exec(func, self, *args, **kwargs)
return safe
@classmethod
def __new(meta, cls, *args, **kwargs):
self = object.__new__(cls, *args, **kwargs)
if 'master' in kwargs:
self.__exec = kwargs['master'].__exec
else:
array = tuple(meta.__REGISTRY.values())
for value in args:
if isinstance(value, array):
self.__exec = value.__exec
break
else:
self.__exec = Affinity()
return self
Run Code Online (Sandbox Code Playgroud)
该代码可以用作构建块,tkinter
通过克隆其类来实现线程安全。该类Affinity
自动确保代码在单个线程上执行,从而防止 GUI 错误。