Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__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__

like image 843
Eli Korvigo Avatar asked Aug 24 '17 14:08

Eli Korvigo


People also ask

What is the __ slots __ attribute used in a class for?

Fixed attributes One of the reasons to use __dict__ is its flexibility after creating the object where you can add new attributes. However, __slots__ will fix the attributes when you create the class. So, it's not possible to add new attributes later.

Should I use __ slots __?

"You would want to use __slots__ if you are going to instantiate a lot (hundreds, thousands) of objects of the same class." Abstract Base Classes, for example, from the collections module, are not instantiated, yet __slots__ are declared for them.

What are slots in Python?

Slots in Python is a special mechanism that is used to reduce memory of the objects. In Python, all the objects use a dynamic dictionary for adding an attribute. Slots is a static type method in this no dynamic dictionary are required for allocating attribute.


1 Answers

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.

like image 60
Andrea Corbellini Avatar answered Nov 14 '22 22:11

Andrea Corbellini