Python中的某些情况感到震惊最近我,其原因仍是没有一点研究后完全清楚。 下面的类定义似乎完美的工作,并会产生什么打算:
class A: __slots__ = 'a', 'b'
class B(A): __slots__ = ()
class C(A): __slots__ = ()
class D(B, C): __slots__ = ()
这些都是安排在一个菱形继承模式四类。 然而,有些类似的模式是不允许的。 下面的类定义看起来好像他们应该具有相同功能的第一:
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
然而, TypeError
在这个例子中提出的。 所以三个问题就出来了:(1)这是Python中的错误,考虑到插槽名称? (2)什么会证明这样的答案吗? (3) 什么是最好的解决办法?
参考文献:
- __slots__和多重继承 (与以下回复)
- 多重继承和__slots__
- 不要使用__slots__
- 多重继承__slots__问题
Answer 1:
不能从多个类定义继承__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?
你的第一个例子工程,因为多重继承只得到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'
实例d,并使用这些时隙:
d = D()
d.a = d.b = d.c = d.d = d.e = 'foo'
我们不能动态地创建变量:
>>> d.f = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'f'
以上是一个方法来解决你的问题的代码,但它可能需要一些重写的-如果你决定B
需要另一个插槽,你必须重构B的功能集成到一个抽象得到d代码重用(这是罚款,但可能混淆)。
它是用抽象最佳实践,和另一个解决办法是要做到这一点,这里的抽象类和/或混入包含您的具体类的功能:
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'
你的第一个例子是挺管用的,因为它避免了冲突的布局,这只是reimagines使用抽象的,而不是结核的解决方案。
最后的问题:
(1)这是Python中的错误,考虑到插槽名称?
没有,尽管很多对此事混乱的,它有点记载和错误尽量让这种行为清楚。
(2)什么会证明这样的答案吗?
定义插槽类获取知道他们的数据去描述位置上。 如果布局发生变化,描述是错误的。
可以在每个子类中创建自己的布局和自己的描述? 我想这可能,但这需要一点它们是如何工作的重写,以及一些政治意愿去做,可以想见,打破了在C API的戳,并依赖于当前的行为的其他用户。
(3)什么是最好的解决办法?
定义“最好的”。
最快写入和可能最不复杂?:只是避免像你的第一个例子中的布局冲突。
最佳实践?:使用抽象继承树,并在结核定义插槽。 虽然可能有这种方法更多的类,它可能是别人可以说是不太复杂和“未来的你”来对待。
Answer 2:
通过强制,没有类定义__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
该代码可以作为一个构建块,使tkinter
线程安全的克隆它的类。 所述Affinity
类自动保证代码在单个线程中执行,从而防止GUI错误。
Answer 3:
我遇到这个错误,我真的想用插槽,用于我的自定义数据库节点。 下面是测试套件我做了(它在Python 3.X):
import logging
A = None, 'attr1', 'attr2', 'attr3', 'attr4'
class C12(object):
__slots__ = (A[1], A[2])
class C1234(object):
__slots__ = (A[1], A[2], A[3], A[4])
class C34(object):
__slots__ = (A[3], A[4])
class C3byC12(C12):
__slots__ = (A[3])
class CEmpty(object):
__slots__ = ()
MSG_FRM = '\n\tc1: {}\n\tc2: {}\n\t__slots__: {}'
NOT_DEF = 'not defined'
def test(c1, c2, slots):
logging.debug('*'*20 + ' new class test ' + '*'*20)
msg = MSG_FRM.format(c1, c2, slots)
try:
if slots == NOT_DEF:
class TestClass(c1, c2): pass
else:
class TestClass(c1, c2):
__slots__ = slots
except TypeError:
logging.exception('BOOM!!! ' + msg)
else:
logging.debug('No Boom! ' + msg)
instance = TestClass()
if '__dict__' in dir(instance):
logging.warning('Instance has __dict__!')
else:
logging.debug('Instance __slots__:{}'.format(
instance.__slots__))
logging.debug('Attributes in instance dir: {}'.format(
' '.join(['X' if (a in dir(instance)) else '_'
for a in A[1:]])))
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
test(C12, C34, (A[2], A[4]))
test(C12, C3byC12, (A[2],))
test(C3byC12, C12, (A[4],))
test(C1234, C34, (A[2], A[4]))
test(C1234, CEmpty, (A[2], A[4]))
test(C12, CEmpty, (A[2], A[4]))
test(C12, CEmpty, (A[1], A[2]))
test(C12, CEmpty, ())
test(CEmpty, C1234, (A[2], A[4]))
test(CEmpty, C12, (A[3],))
test(C12, C34, NOT_DEF)
test(C12, CEmpty, NOT_DEF)
下面是结果:
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
c1: <class '__main__.C12'>
c2: <class '__main__.C34'>
__slots__: ('attr2', 'attr4')
Traceback (most recent call last):
File "boom.py", line 30, in test
class TestClass(c1, c2):
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
c1: <class '__main__.C12'>
c2: <class '__main__.C3byC12'>
__slots__: ('attr2',)
Traceback (most recent call last):
File "boom.py", line 30, in test
class TestClass(c1, c2):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases C3byC12, C12
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C3byC12'>
c2: <class '__main__.C12'>
__slots__: ('attr4',)
DEBUG:root:Instance __slots__:('attr4',)
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
c1: <class '__main__.C1234'>
c2: <class '__main__.C34'>
__slots__: ('attr2', 'attr4')
Traceback (most recent call last):
File "boom.py", line 30, in test
class TestClass(c1, c2):
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C1234'>
c2: <class '__main__.CEmpty'>
__slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C12'>
c2: <class '__main__.CEmpty'>
__slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X _ X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C12'>
c2: <class '__main__.CEmpty'>
__slots__: ('attr1', 'attr2')
DEBUG:root:Instance __slots__:('attr1', 'attr2')
DEBUG:root:Attributes in instance dir: X X _ _
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C12'>
c2: <class '__main__.CEmpty'>
__slots__: ()
DEBUG:root:Instance __slots__:()
DEBUG:root:Attributes in instance dir: X X _ _
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.CEmpty'>
c2: <class '__main__.C1234'>
__slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.CEmpty'>
c2: <class '__main__.C12'>
__slots__: ('attr3',)
DEBUG:root:Instance __slots__:('attr3',)
DEBUG:root:Attributes in instance dir: X X X _
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
c1: <class '__main__.C12'>
c2: <class '__main__.C34'>
__slots__: not defined
Traceback (most recent call last):
File "boom.py", line 28, in test
class TestClass(c1, c2): pass
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
c1: <class '__main__.C12'>
c2: <class '__main__.CEmpty'>
__slots__: not defined
WARNING:root:Instance has __dict__!
DEBUG:root:Attributes in instance dir: X X _ _
正如你可以看到你有两种选择:
- 任一定义
__slots__ = ()
对于所有但父类中的一个, - 或者让父母之一的另一个子类。
请注意,您应该定义__slots__
在新的课呢,否则得到一个__dict__
。
Answer 4:
你见过这样的选择? https://stackoverflow.com/a/53063670/1400467
有一个“棘手”的解决方法通过使用元类和假_slots_
属性。 这工作在Python 3.6和Python中3.X希望如此。
文章来源: Cannot inherit from multiple classes defining __slots__?