Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling class method as part of initialization

Tags:

python

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

like image 946
Gloweye Avatar asked Apr 19 '16 12:04

Gloweye


People also ask

Can you call a class method in init?

We can call other methods of the class from the __init__ method by using the self keyword. The above code will print the following output.

Can I call a method inside a constructor in Python?

Solution 1. A constructor can call methods, yes. A method can only call a constructor in the same way anything else can: by creating a new instance.

What does __ call __ do in Python?

The __call__ method enables Python programmers to write classes where the instances behave like functions and can be called like a function. When the instance is called as a function; if this method is defined, x(arg1, arg2, ...) is a shorthand for x.

What is __ init __ called?

"__init__" is a reseved method in python classes. It is called as a constructor in object oriented terminology. This method is called when an object is created from a class and it allows the class to initialize the attributes of the class.


1 Answers

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.

like image 147
Bakuriu Avatar answered Oct 23 '22 14:10

Bakuriu