Search code examples
pythonpython-3.xpython-3.5typingslots

__slots__ conflicts with a class variable in a generic class


I've got conflicts between Python's typing system and __slots__. Here is a small reproducible example.

from typing import TypeVar, Generic, Sequence

T = TypeVar("T")

class TestGeneric(Sequence, Generic[T]):
    __slots__ = ("test",)

    def __init__(self, test: T):
        self.test = [test]

    def __iter__(self):
        return iter(self.test)
    def __len__(self):
        return len(self.test)

    def __contains__(self, item):
        return item in self.test

    def __getitem__(self, _):
        return self.test[0]

Now whenever I try to specify a content type, e.g.

V = TestGeneric[int]

I get

ValueError: 'test' in __slots__ conflicts with class variable

I use Generics in classes without slots a lot, hence I think this error has to be linked to __slots__. Moreover, the same class works fine, if you remove the __slots__


Solution

  • I would say that this is a bug in the typing module, which does not properly take into account __slots__ when creating new types.

    This problem can be reproduced with this very short example:

    >>> class MyClass:
    ...   __slots__ = ('my_member',)
    ... 
    >>> type('MySubClass', (MyClass,), dict(MyClass.__dict__))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: 'my_member' in __slots__ conflicts with class variable
    

    Type call to type() above is the equivalent of what is happening behind the scenes in the typing module.

    This exception is caused by the fact that when you use __slots__, the members you specify are automatically added into the type dict:

    >>> MyClass.__slots__
    ['my_member']
    >>> MyClass.__dict__
    mappingproxy({..., 'my_member': <member 'my_member' of 'MyClass' objects>, ...})
    

    When we do type('MySubClass', (MyClass,), dict(MyClass.__dict__)), we are passing my_member twice: once via MyClass.__slots__, once via MyClass.__dict__, and the type machinery is complaining about it.

    There's not much you can do about it, other than avoiding using __slots__ or calling register() instead of subclassing.