5

Current Status

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

The Issue

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.

Solution 1

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.

Solution 2

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

1 Answer 1

6

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:

  • It is more general because you can do stuff before the class is created, control how it is created and do something afterwards. A decorator receives the class object when it is completed and can only do post-creation stuff
  • They are inherited, so you don't have to add anything explicit for each class, but only to the base class.

I'm not going into details here. Check out What is a metaclass in Python? for more information on metaclasses.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! That helps. I never knew about __subclasses__ before. And it's making my work a lot easier. I'll also read up a bit about metaclasses, which is also new to me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.