9

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__ = []
xsubira
  • 474
  • 1
  • 5
  • 14
  • As explained in this comment ( https://stackoverflow.com/questions/10857515/cannot-inherit-from-multiple-classes-defining-slots#comment91059965_10857515 ) it seems to collide with a low level design of python objects structure what makes incompatible work with slots and multiple inheritance together. – xsubira Oct 30 '18 at 09:15
  • Yep, related: https://mail.python.org/pipermail/python-list/2004-December/293157.html – juanpa.arrivillaga Oct 30 '18 at 09:16
  • There's a similar question [here](https://stackoverflow.com/questions/10857515/cannot-inherit-from-multiple-classes-defining-slots), but the answers are completely useless. – Aran-Fey Oct 30 '18 at 09:19

3 Answers3

8

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.

Community
  • 1
  • 1
MT-FreeHK
  • 2,462
  • 1
  • 13
  • 29
  • You are terribly right. I tried to do using metaclasses but is a no way out, definitely. Thank you very much. – xsubira Oct 30 '18 at 10:29
  • Please @MatrixTai, can you help me considering the pros and cons of my own answer? (is down below) – xsubira Oct 30 '18 at 11:51
  • @MatrixTai You don't have `__slots__` in `A` and `B` so you aren't using this feature at all. `C().x = 100` doesn't fail. – WloHu Nov 29 '18 at 16:30
  • @JCode, no, you have `__slots__` in `A` and `B`, inherited from the base. Try `A().a`, it doesn't fail. – MT-FreeHK Nov 30 '18 at 01:26
  • 1
    @MatrixTai You don't get it. `__slots__` aren't inherited. The thing is that if `C().x = 100` doesn't fail when `x` is not listed in any `__slots__` means that the feature doesn't work. You miss one `__slots__` in `B` class hierarchy and you have `__dict__` available again in subclasses of `B`. Just run your code in interpreter and assign `C().x`. – WloHu Nov 30 '18 at 08:37
  • @JCode, you are correct, my method does not work as well, just erasing all `__slots__`, I think about it. – MT-FreeHK Nov 30 '18 at 09:49
  • 1
    @MatrixTai Just add empty `__slots__` to `A` and `B`. The thing I'm talking about is in the doc you've linked: "However, child subclasses will get a `__dict__` and `__weakref__` unless they also define `__slots__` (which should only contain names of any additional slots)." I understand you may missed it because the doc is poorly written IMO. – WloHu Nov 30 '18 at 10:26
  • @JCode, oh yes, I completely miss this line. After reviewing my code, I find out I have added `__slots__` for subclass, while I completely forget about this after years. Thank you very much. – MT-FreeHK Dec 01 '18 at 02:17
1

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'
xsubira
  • 474
  • 1
  • 5
  • 14
  • Please @MatrixTai, can you help me considering the pros and cons of my own answer? – xsubira Oct 30 '18 at 11:50
  • 2
    Nope, you can't do this, class A and B won't have any slots in this sense. You cannot assign `__slots__` after class build. You may do this, only when you never use class A, B individually. – MT-FreeHK Oct 31 '18 at 01:30
0

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')
... 
flexatone
  • 345
  • 3
  • 5