Reputation: 2470
When inheriting from a class, the child class is accessible on the parent via the .__subclasses__()
method.
class BaseClass:
pass
class SubClass(BaseClass):
pass
BaseClass.__subclasses__()
# [<class '__main__.SubClass'>]
However, deleting the child class doesn't seem to remove it from the parent.
del SubClass
BaseClass.__subclasses__()
# [<class '__main__.SubClass'>]
__subclasses__
get its information from? And can I manipulate it?Or
BaseClass.remove_subclass(SubClass)
?Upvotes: 6
Views: 1748
Reputation: 2470
Where does
__subclasses__
get its information from?
For the CPython implementation of Python, the type object keeps a list of weak references under PyTypeObject.tp_subclasses
. This is marked as "Not inherited. Internal use only" in the docs, so can be treated as an implementation detail of CPython. See also: How is __subclasses__
method implemented in CPython?.
And can I manipulate it?
Any class has a .__bases__
descriptor which, if changed, updates the references in PyTypeObject.tp_subclasses
.
.__bases__
can only be manipulated when the class doesn't directly inherit from object
. So while:
class BaseClass: pass
class OtherClass(BaseClass): pass
and
class BaseClass: pass
class OtherClass: pass
BaseClass.__bases__ = (OtherClass, )
# TypeError: __bases__ assignment: 'BaseClass' deallocator differs from 'object'
should be equivalent *. You will get an error. See: https://bugs.python.org/issue672115
You also can't use this to change a class to inherit from object.
class BaseClass: pass
class SubClass(BaseClass): pass
SubClass.__bases__ = (object,)
# TypeError: __bases__ assignment: 'type' object layout differs from 'BaseClass'
You can, however, change the bases of a class to be another class.
class BaseClass: pass
class SubClass(BaseClass): pass
class OtherClass: pass
SubClass.__bases__ = (OtherClass, )
# Or don't define it.
SubClass.__bases__ = (type("OtherClass", (object, ), {}), )
This all updates the parent class:
>>> BaseClass.__subclasses__()
[]
Upvotes: 1
Reputation: 95722
The subclass contains references to itself internally, so it continues to exist until it is garbage collected. If you force a garbage collection cycle it will disappear from the __subclasses__()
:
import gc
gc.collect()
and then it has gone.
However make sure you have deleted all other references to the class before you force the garbage collection. For example, if you do it interactively and the last output was the subclass list there will still be a reference to the class in _
.
class BaseClass:
pass
class SubClass(BaseClass):
pass
print(BaseClass.__subclasses__())
# [<class '__main__.SubClass'>]
del SubClass
import gc
gc.collect()
print(BaseClass.__subclasses__())
# []
Output with python 3.7 is:
[<class '__main__.SubClass'>]
[]
I should probably also add that while garbage collection works for this simple case you probably shouldn't depend on it in real life: it would be far too easy to accidentally keep a reference to the subclass somewhere in your code and then wonder why the class never goes away.
What you are trying to do here is keep a registry of subclasses so that the factory can return an object of the appropriate class. If you want to be able to add and remove classes from the registry then I think you have to be explicit. You could still use __subclasses__
to find candidate classes, but keep a flag on each class to show whether it is enabled. Then instead of just deleting the subclass set the flag to show the class is no longer in use and then (if you want) delete it.
Upvotes: 6