Reputation: 1408
I have an abstract base class that, which hosts data in the form of a numpy array, knows how to work this data, and which can explain matplotlib how to draw it. To accomodate different types of data, it has a number of subclasses, like this:
class PlotData():
"""Base Class"""
subclasslist = []
@classmethod
def register(cls):
super().subclasslist.append(cls)
def __new__(self, initdata, *args, **kwargs):
for subclass in subclasslist:
try:
subclass.__test__(initdata)
except AssertionError:
continue
else:
break
else:
raise TypeError("Initdata does not fit any known subclass")
return subclass(initdata, *args, **kwargs)
class Plot3D(PlotData):
"""Subclass for 3d-plotting data"""
def __test__(initdata):
assert Data_is_the_right_kind
class Plot_XY(PlotData):
"""Subclass for for plotting X-Y relations of data"""
def __test__(initdata):
assert Data_is_the_right_kind
now, the issue is how to get the class references into the subclasslist. At first I wanted to call super().register() in the class body, but im unable to get a reference to the class itself, which is what I want to store in the list. A small search has yielded two possible solutions, and I was wondering what the best one was.
Adding a call after each class definition, like this:
class Plot_XY(PlotData):
"""Subclass for for plotting X-Y relations of data"""
def __test__(initdata):
assert Data_is_the_right_kind
Plot_XY.register()
This works, but seems like a very dirty solution to me - a very important part of the class structure is located outside of the body.
Another possibility could be class decorators. However, I've never used them before, and the examples I've found are generally used to override/add functionality to methods. (here and here, for example). I am familiar with function decorators though, and the following should roughly make clear what I'm aiming for (and a dumbed down version works in the interpreter):
def some_creative_decorator_name(cls):
cls.register()
return cls
or at least, something that functions like Solution 1 but looks like:
@some_creative_decorator_name
class Plot_XY(PlotData):
"""Subclass for for plotting X-Y relations of data"""
def __test__(initdata):
assert Data_is_the_right_kind
It seems to work just as well, but will this screw up stuff like inheritance? That was one of the concerns noted in the linked pages, and I don't really dare count to much on it. (I am not expecting people to subclass it further, but I don't really want to make it impossible if it's desired.)
(Of course other solutions are welcome as well.)
Upvotes: 5
Views: 89
Reputation: 101919
What you are doing is useless because it's already provided:
>>> class A(object):pass
...
>>> class B(A):pass
...
>>> class C(A): pass
...
>>> A.__subclasses__()
[<class '__main__.B'>, <class '__main__.C'>]
>>>
There is no need to keep your own subclasslist
when python already provides one for you.
Note that this doesn't include subclasses of subclasses:
>>> class D(B):pass
...
>>> A.__subclasses__()
[<class '__main__.B'>, <class '__main__.C'>]
However it is easy enough to find all the subclasses:
>>> def all_subclasses(klass):
... for sub in klass.__subclasses__():
... yield sub
... yield from all_subclasses(sub)
...
>>> list(all_subclasses(A))
[<class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>]
This said, if you want to replicate this functionality it is easier to look at how the default method works. And you'd discover that:
>>> '__subclasses__' in dir(object)
False
>>> '__subclasses__' in dir(type)
True
So here you can see that it is a method of type
which is the metaclass of object
. The way to properly replicate this is to write your custom metaclass.
Basically a metaclass is similar to the decorator approach however:
I'm not going into details here. Check out What is a metaclass in Python? for more information on metaclasses.
Upvotes: 6