简体   繁体   中英

Python 3.6.5 "Multiple bases have instance lay-out conflict" when multi-inheritance of classes having __slots__

If I run this code, I'v got the subject error message. But why? And how to avoid it getting the C class having its parents slots?

class A():
        __slots__ = ['slot1']

class B():
        __slots__ = ['slot2']

class C(A, B):
        __slots__ = []

Simply speak, you just cannot do it.

As stated in Documentation ,

Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots (the other bases must have empty slot layouts) - violations raise TypeError.

The idea behind __slots__ is to reserve specific slots for each attribute in the memory layout of your instances. A and B are trying to reserve the same part of their memory layout for the slot1 and slot2 attributes, and C can't have the same memory reserved for two attributes. It's just not compatible.


Thanks for JCode metioned in comment, the following method is modified to be correct.

But there is always the way, I personally prefer to use a common base contained all required slots if __slots__ is necessary while there is multiple inherited class.

import pympler.asizeof
class base():
    __slots__ = ['a','b']

class A(base):
    __slots__ = []

class B(base):
    __slots__ = []

class C(A,B):
    __slots__ = []

class D():
    pass

#Update
bb = base()
bb.a = 100
bb.b = 100
print(pympler.asizeof.asizeof(bb))
a = A()
a.a = 100
a.b = 100
print(pympler.asizeof.asizeof(a))
c = C()
c.a = 100
c.b = 100
print(pympler.asizeof.asizeof(c))
d = D()
d.a = 100
d.b = 100
print(pympler.asizeof.asizeof(d))

Update The 4 values will be 88, 88, 88, 312. Though __slots__ reserved.

It had (in my opinion) a silly workaround. That's why no TypeError is raised when __slots__ is empty, and having an empty __slots__ attribute preserves the "wondered" python behaviour what warns when assigning to an attribute not defined in __slots__ .

So, consider the following metaclass :

class SlotBase(type):
    def __new__(cls,name,bases,dctn):
        if ('_slots_' in dctn) and not ('__slots__' in dctn):
            dctn['__slots__'] = []
        elif '__slots__' in dctn:
            for base in bases:
                if hasattr(base,'_slots_'):
                    dctn['__slots__'] += getattr(base,'_slots_')
        return super().__new__(cls,name,bases,dctn)

An then deploy on base classes.

class A(metaclass=SlotBase):

    _slots_=['slot1'] #fake __slots__ attribute

    classPropertyA = 'Some silly value'

    def functA(self):
        print('I\'m functA')

class B(metaclass=SlotBase):

    _slots_=['slot2'] #fake __slots__ attribute

    classPropertyB = 'Some other silly value'

    def functB(self):
        print('I\'m functB')

class C(A,B):
    __slots__ = []

    classPropertyC = 'Just another silly value'

If we execute following code

c=C()
c.classPropertyC
c.classPropertyA
c.functA()
c.functB()
c.slot1='Slot exists then assignment is accepted'
c.slot3='Slot does not exists then assignment couldn\'t be accepted'

This produces following output

Just another silly value
Some silly value
I'm functA
I'm functB
Traceback (most recent call last):
  File "/tmp/slots.py", line 41, in <module>
    c.slot3='Slot does not exists then assignment couldn\'t be accepted'
AttributeError: 'C' object has no attribute 'slot3'

For using multiple-inheritance with slotted classes, a practical option is have only one parent class have non-empty slots. The remaining classes then serve as "mixins" with defined (but empty) slots. Then, in the child class, simply define the final slots as needed.

As already shown, multiple inheritance when all parents define non-empty slots is problematic.

>>> class B: __slots__ = ('a', 'b')
... 
>>> class C: __slots__ = ('a', 'b')
... 
>>> class D(C, B): __slots__ = ('a', 'b')
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict

>>> class D(C, B): __slots__ = ('a', 'b', 'c')
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict

The approach suggested here makes C into a "mixin" class that defines empty slots. Then the child class, using multiple inheritance, can simply define the slots to be whatever is needed.

>>> class B: __slots__ = ('a', 'b')
... 
>>> class C: __slots__ = ()
... 
>>> class D(C, B): __slots__ = ('a', 'b')
... 
>>> class D(C, B): __slots__ = ('a', 'b', 'c')
... 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM