Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
259 views
in Technique[技术] by (71.8m points)

python - Cannot inherit from multiple classes defining __slots__?

A certain situation in Python recently alarmed me, and its reason is still not completely clear after a little research. The following class definitions appear to work flawlessly and will produce what is intended:

class A: __slots__ = 'a', 'b'
class B(A): __slots__ = ()
class C(A): __slots__ = ()
class D(B, C): __slots__ = ()

These are four classes arranged in a diamond inheritance pattern. However, a somewhat similar pattern is not allowed. The following class definitions seem as though they should function the same as the first:

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

However, a TypeError is raised in this example. So three questions arise: (1) Is this a bug in Python, considering the slot names? (2) What would justify such an answer? (3) What is the best workaround?


References:

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Cannot inherit from multiple classes defining __slots__?

Close.

You cannot inherit from multiple classes defining nonempty __slots__ when there is a layout conflict.

Slots have an ordered layout, and the descriptors that get created in the class rely on those positions, therefore they must not have a layout conflict under multiple inheritance.

Your simplest approach fails because each a and b are considered different slots, and the layout algorithm does not check whether they are semantically the same:

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?

Your first example works because the multiple inheritance gets only A's slots, thus all cases are using A's descriptors and positions/layout. For example, the following would be allowed:

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'

Instantiating D, and using those slots:

d = D()
d.a = d.b = d.c = d.d = d.e = 'foo'

And we cannot dynamically create variables:

>>> d.f = 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'f'

The above is one approach to solving your problematic code, but it could require a bit of rewriting - if you decide B needs another slot, you've got to refactor B's functionality into an abstraction to get code reuse for D (which is fine, but potentially confusing).

It's a best practice to use abstractions, and another solution would be to do this, where the abstract classes and/or mixins contain the functionality for your concrete classes:

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'

Your first example is quite workable because it avoids a layout conflict, this just reimagines a solution using abstractions instead of concretions.

Final Questions:

(1) Is this a bug in Python, considering the slot names?

No, in spite of lots of confusion on the matter, it's somewhat documented and the errors try to make this behavior clear.

(2) What would justify such an answer?

Classes that define slots get descriptors that know where their data goes positionally. If layouts change, the descriptors would be wrong.

Could each subclass create its own layout and its own descriptors? I suppose it could, but that would require a bit of rewriting of how they work, and some political will to do it, and could conceivably break other users that are poking around in the C api and relying on the current behavior.

(3) What is the best workaround?

Define "best".

Fastest to write and possibly least complex?: just avoid layout conflicts like in your first example.

Best practices?: Use abstract inheritance trees, and define slots in your concretions. While there may be more classes with this approach, it may be arguably less complex for others and "future-you" to deal with.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...