Slots class holding a reference to the original version #407
I have a class hierarchy implemented with slots enabled, like this:
@attr.s(slots=True) class BaseClass(object): foo = attr.ib(default='foo') @attr.s(slots=True) class SubClass(BaseClass): bar = attr.ib(default='bar')
This works fine until I wanted to introspect the class hierarchy:
BaseClass.__subclasses__() # returns [<class '__main__.SubClass'>, <class '__main__.SubClass'>]
One of these is the original class, and the other is the new one created and returned by
My understanding was that classes kept weak references to their subclasses to allow the
I guessed that there might be a reference cycle somewhere, so I tried adding a call to
Because I didn't see that the reference-counting mentioned in this comment was ever addressed, I also guessed that this might be a leak due to the
The text was updated successfully, but these errors were encountered:
That would answer the question, but I'm not convinced that it's true...
Python 3.6.6 (default, Jun 27 2018, 13:11:40) [GCC 8.1.1 20180531] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import attr >>> @attr.s(slots=True) ... class Foo: pass ... >>> class Bar(Foo): pass ... >>> AttrsBar = attr.s(slots=True)(Bar) >>> issubclass(AttrsBar, Bar) False
Hm that might be due to how we create the class (by calling
>>> import attr >>> class C: pass >>> C2 = attr.s(slots=True)(C) >>> C2.__mro__ (<class '__main__.C'>, <class 'object'>)
So the class is definitely there, it’s just that Python subclass machinery doesn’t know about it.
I think you're being misled by the fact that the class generated by attrs still thinks its name is
>>> import attr >>> class C: pass ... >>> C2 = attr.s(slots=True)(C) >>> C2 <class '__main__.C'> >>> C2.__mro__ is C False >>> C2.__mro__ is C2 True
When the class is created, it's using the original classes bases as the new bases, and I didn't see anywhere that the bases get modified...
Ah, no I hadn't thought of that...
I think that means the correct fix is to add
>>> import attr >>> @attr.s(slots=True) ... class C: pass ... >>> @attr.s(slots=True) ... class C2(C): pass ... >>> import gc >>> gc.collect() 11 >>> C.__subclasses__() [<class '__main__.C2'>]
The original class is left as a cyclic isolate when
Proper type annotations are needed by dict2object, which is done here. There's also a weird bug with attrs slots and __subclasses__, so garbage collection is also called. See python-attrs/attrs#407