2

I have a class hierarchy that I want to register so I can later access it. So far I've been doing it manually:

class FirstClass(MyBaseClass):
    ....
registry.register(FirstClass)

class SecondClass(FirstClass):
    ....
registry.register(SecondClass)

I'm looking for a way to call registry.register(Class) when the class is evaluated (that is, when someone imports the package with the class), without having to call it explicitly.

I guess I have to add something to MyBaseClass, but I couldn't figure out what. All the special methods seem to be instance related, not class related.

Is there a way to do that?

Explanation: Registry keeps track of the classes derived from BaseClass. At some point in the code, I go over all these classes and instantiate objects. It is a bit more complicated than that, as registry is doing other things as well, but that's the main idea.

zmbq
  • 38,013
  • 14
  • 101
  • 171

3 Answers3

2

This is exactly what metaclasses are for: apply an operation to an entire hierarchy of classes:

In [1]: def register(cls):
   ...:     # just to show when it is called.
   ...:     print('Register: {}'.format(cls))
   ...: 
   ...: 
   ...: class MyMeta(type):
   ...:     def __new__(cls, name, bases, attrs):
   ...:         the_cls = super().__new__(cls, name, bases, attrs)
   ...:         register(the_cls)
   ...:         return the_cls

In [2]: class FirstClass(metaclass=MyMeta):
   ...:     pass
   ...: 
Register: <class '__main__.FirstClass'>
In [3]: class SecondClass(FirstClass):
   ...:     pass
   ...: 
Register: <class '__main__.SecondClass'>

Note that you have to add the metaclass only in the root of the hierarchy and it will be inherited in all child classes, so you have to simply add it for MyBaseClass and you are done.

For an in-depth explanation see this SO question: What is a metaclass in Python?


I used python3 syntax. In python2 to add a metaclass you have to set the __metaclass__ attribute:

class MyClass(object):
    __metaclass__ = MyMeta

I'd like to point out that to get the subclasses of a class you can use the __subclasses__ method. There is no need to keep track of them explicitly, not to use a custom metaclass:

In [4]: FirstClass.__subclasses__()
Out[4]: [__main__.SecondClass]
Community
  • 1
  • 1
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
2

Python automatically keeps track of the immediate subclasses of any new-style classes (classes withobjectas their base class or a base class derived thusly) define in a class attribute named__subclasses__. This means you don't have to manually register them yourself.

To get an entire inheritance hierarchy, you'll need to collect what's in the attribute for each subclass recursively. This is what I mean:

class MyBaseClass(object): pass
class FirstClass(MyBaseClass): pass
class SecondClass(FirstClass): pass

def get_subclasses(cls):
    return cls.__subclasses__() + [g for s in cls.__subclasses__()
                                    for g in get_subclasses(s)]

print(get_subclasses(MyBaseClass))

Output:

[<class '__main__.FirstClass'>, <class '__main__.SecondClass'>]
martineau
  • 119,623
  • 25
  • 170
  • 301
0

My recent project had a requirement to keep track of Classes and my initial approach was to use globals() or something like that to lookup classes , however the pythonistas at irc.freenode.org #python suggested using decorators like this and it looks really pythonic:

registry = {}
def registered(cls):
    registry[cls.__name__] = cls
    return cls

@registered
class Foo:
    """
    """
@registered
class Bar:
    """
    """

When you import this file , file.registry will have all the classes inside it and you cal look it up with registry['Class_Name'] . In our use case , we chose our Class Implementation based on the Class_Name configured .

Nishant
  • 20,354
  • 18
  • 69
  • 101
  • This is similar to my original approach. The problem is sometimes I (or other users of the library) forget, and that leads to unnecessary bugs. – zmbq Jun 15 '14 at 15:57