attrs
attrs copied to clipboard
Generic slots classes don't work
Attempting to create a generic attrs class with slots=True
fails if it does not inherit from some other generic class.
from typing import TypeVar, Generic, Container
from unittest import TestCase
from attr import attrs
T = TypeVar('T')
class TestGenericBug(TestCase):
def test_no_slots_ok(self):
@attrs
class Foo(Generic[T]):
pass
def test_no_attrs(self):
class Meep(Generic[T]):
__slots__ = ()
def test_frozen_with_generic_parent_ok(self):
@attrs(frozen=True)
class Foo2(Generic[T], Container[T]):
def __contains__(self, x: object) -> bool:
return False
# Failure is here
def test_slots_with_no_parent(self):
with self.assertRaisesRegex(TypeError, "Cannot inherit from plain Generic"):
@attrs(slots=True)
class Foo4(Generic[T]):
pass
The full failure stack trace (if we remove the assertRaises
is:
File "/Users/gabbard/repos/isi-flexnlp/tests/util/test_generic_bug.py", line 27, in test_slots_with_no_parent
class Foo4(Generic[T]):
File "/Users/gabbard/anaconda3/envs/flexnlp/lib/python3.6/site-packages/attr/_make.py", line 637, in wrap
return builder.build_class()
File "/Users/gabbard/anaconda3/envs/flexnlp/lib/python3.6/site-packages/attr/_make.py", line 372, in build_class
return self._create_slots_class()
File "/Users/gabbard/anaconda3/envs/flexnlp/lib/python3.6/site-packages/attr/_make.py", line 442, in _create_slots_class
cd,
File "/Users/gabbard/anaconda3/envs/flexnlp/lib/python3.6/typing.py", line 948, in __new__
raise TypeError("Cannot inherit from plain Generic")
TypeError: Cannot inherit from plain Generic
I am using attrs 17.3.0
This is something about meta classes, isn’t it? 😐
I ran into this also (on 18.1), and discovered that oddly enough defining __slots__
myself and using attr.s(these={...})
does work.
simplified example:
@attr.s(these={'foo': attr.ib()})
class Foo(Generic[T]):
__slots__ = ['foo']
I have tested that slots work correctly in this case.
I don't really know what that means, but maybe that's useful for narrowing it down?
Confirming the bug in 18.2.0.
This does work with python 3.7, at least with __future__.annotations
, I haven't tested it without.
I used the workaround from @dcbaker, but you can use slots=True
instead of defining it manually:
from typing import Generic, TypeVar
import attr
T = TypeVar('T')
@attr.s(these={'foo': attr.ib()}, slots=True)
class Foo(Generic[T]):
pass
f = Foo(1)
assert not hasattr(f, '__dict__')
assert f.__slots__ == ('foo',) # attrs 18.1
assert f.__slots__ == ('foo', '__weakref__') # attrs 20.1
Edit: This only works on 3.7+ apparently
To save future readers from confusion: the workaround in commit https://github.com/matrix-org/synapse/commit/d15d241f5ebc978328494ee350d4a0ef8e0070f5 above does not work and was discarded after it was found not to work.
(People on our team keep coming across this issue and thinking 'oh look, (other team member) already found a workaround!' only to be confused when it doesn't do the trick..)
As this bug occurs only in Python <= 3.6, and the next version will drop Python 3.6 support, maybe this issue should be closed?
yeah looks like this is not an issue on 3.7+ anymore – thanks!