attrs icon indicating copy to clipboard operation
attrs copied to clipboard

Generic slots classes don't work

Open gabbard opened this issue 6 years ago • 6 comments

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

gabbard avatar Dec 12 '17 18:12 gabbard

This is something about meta classes, isn’t it? 😐

hynek avatar Dec 15 '17 13:12 hynek

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?

dcbaker avatar Jun 01 '18 23:06 dcbaker

Confirming the bug in 18.2.0.

leftys avatar Sep 17 '18 12:09 leftys

This does work with python 3.7, at least with __future__.annotations, I haven't tested it without.

dcbaker avatar Sep 17 '18 20:09 dcbaker

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

RazerM avatar Aug 24 '20 21:08 RazerM

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..)

reivilibre avatar Oct 07 '21 16:10 reivilibre

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?

jhominal avatar Mar 16 '23 17:03 jhominal

yeah looks like this is not an issue on 3.7+ anymore – thanks!

hynek avatar Mar 17 '23 05:03 hynek