This is the new version of my old article. It was amended and supplemented for Python etc. — telegram channel about Python and more.

Let’s delve into a pretty typical situation. You have some subclasses, and you have to pick one according to some parameter provided by a user (usually called type or something like this).

Here is an example: your user sends a message via your service specifying a type of the message and some extra parameters.

This code works quite well, but there are some techniques you can use to improve it.

Factory

First of all, I want a separate entity to care about message objects creation. There are two conventional types of that entity: factory or factory method.

Factory:

Factory method:

For me, the factory is somewhat redundant here, so I stick to the factory method approach in the following examples.

Choose class, not object

Now we can improve the code of factory method itself. For instance, since classes are first class objects in Python, you don’t really need to choose a message object, just choose a class:

Use mapping

Now it’s obvious that we deal with a dull mapping of types to classes. So it may be a good idea to transform our if statements to an actual map:

Note, that we can’t make MESSAGE_TYPE_TO_CLASS_MAP a class attribute since descendants of BaseMessage should be declared after BaseMessage and therefore not yet exist in the time of BaseMessage creation.

Generate class name (don’t)

You may notice that the name of every class almost matches the name of the type. We can use that to generate class name by type name and don’t store an actual mapping:

But you actually shouldn’t do this. There are many problems with this approach. First of all, you can’t guarantee that some class called SomethingMessage will be found despite the fact you don’t want it to. Second, it’s not as obvious as it can be. Third, your IDE probably won’t understand a thing in this mess.

If for some reason you decide to stick to this method, you should at least check whether the result class is a subclass of BaseMessage .

Register subclasses

The plain mapping we use is still not perfect. If you create a new message subclass, you have to both define it and add to the mapping. It would be a good idea to mark the subclass somehow, so it will appear in the mapping automatically. And this is where python decorators shine.

Remember I said we can’t make the mapping a BaseMessage attribute because descendants are not yet defined? Now we can make them add themselves to the mapping upon declaration.

For this, we need subclasses class attribute and register_subclass decorator to add a subclass to that attribute. Note, that register_subclass is a parameterized decorator, so it returns the function that accepts a subclass as an argument.

This approach has been working for me pretty well for a long time. The best thing about it is that every time a new programmer wants to create a new message subclass, he merely copies an existing one as a scaffold and never forgets to add the decorator.

Metaclass

Alternatively, you can take advantage of the fact that metaclass of some class also works for all of its descendants. We may write metaclass for BaseMessage that automatically registers all of the subclasses.

Subclass still needs to store a type, let’s put it in _MESSAGE_TYPE attribute. Also, metaclass needs some way to detect BaseMessage . To do that, we use the fact that it’s the first class created via metaclass.

Look at the result:

If you can’t quite grasp what is going on here, there is no surprise. Solutions that use metaclasses are notorious for being overcomplicated and hard to read and understand. And there are a little we gained this way: subclasses need to specify _MESSAGE_TYPE anyway, that is not any simpler than @register decorator.

Also, classes with custom metaclasses may be hard to inherit from. If you want to inherit from two classes, their metaclasses should be equal or one them should be a descendant of another to avoid so-called metaclass conflicts.

Init subclass hook

Since Python 3.6 it’s possible to hook subclass creation without using a metaclass, you can do it via __init_subclass__ magic method:

You may or may not find it more elegant than decorators solution.

Importing

All above solutions are suitable only for subclasses that present in the same file that base class or at least at any other that are already imported for some reason. Without it, they wouldn’t have a chance to be registered.

To fix that you may import them manually, one by one (which is tedious) or organize some auto-discovering (which may be insecure). You also may use mapping solution and import modules dynamically upon objects creation, but it’s even uglier. Sadly, I still don’t have any elegant solution for this problem.