3

I want to register classes to a manager after class was loaded, like http handlers register to a handler manager.

It can be done in other ways, such as define the relationship in a map or call a register function after class definition.

But is there a better way to do this automatically?


update:

While Dunes's answer filled my need. I'm trying to improve the question and make it more useful for others who meet the same problem.

Here are the examples.

handler/__init__.py

handler/basehandler.py - classBase, HandlerManager

handler/handlerA.py - classA(classBase)

handler/hanlderB.py - classB(classBase)

handlerA and handlerB contains classA and classB, which are subclasses of classBase.

classA handlers requests from /a/, classB handlers /b/

I need to register them to HandlerManager automatically at the first time the handler module is imported.

Albert Tsai
  • 171
  • 2
  • 9

4 Answers4

7

If "being loaded" here means "being imported" (at the first time), then class decorator is an solution. Below sample code is copied from this page

registry  =  {}

def register(cls):
    registry[cls.__clsid__] = cls
    return cls

@register
class Foo(object):
    __clsid__ = "123-456"

def bar(self):
    pass
Lei Shi
  • 757
  • 4
  • 8
3

Seems like a possible use for metaclasses. It's rare to need to use metaclasses for anything -- they're overkill for pretty much everything. And most things that can be achieved using a meta class can be more easily achieved using decorators. However, this way you can ensure that any subclass of your base handler will automatically be registered too (unless it asks to not be registered).

class HandlerManager:
    handlers = []
    @classmethod
    def register(cls, handler):
        print("registering", handler)
        cls.handlers.append(handler)

class HandlerRegisterer(type):
    def __init__(self, name, bases, attrs, register=True):
        super().__init__(name, bases, attrs)
        if register:
            HandlerManager.register(self)

    def __new__(metaclass, name, bases, attrs, register=True):
        return super().__new__(metaclass, name, bases, attrs)

class BaseHandler(metaclass=HandlerRegisterer, register=False):
    # not actually a real handler, so don't register this class
    pass

class MyHandler(BaseHandler):
    # only have to inherit from another handler to make sure this class
    # gets registered.
    pass

print(HandlerManager.handlers)
assert BaseHandler not in HandlerManager.handlers
assert MyHandler in HandlerManager.handlers

If you need to use abstract classes then you will need to make your meta class subclass ABCMeta. This is because abstract classes are achieved by using meta classes, and python only allows a class to have one meta class. By subclassing ABCMeta you make the two subclasses compatible (there's no code in either one that conflicts with the other).

from abc import ABC, ABCMeta, abstractmethod

class HandlerRegisterer(ABCMeta):
    # subclass ABCMeta rather than type
    def __init__(self, name, bases, attrs, register=True):
        super().__init__(name, bases, attrs)
        if register:
            HandlerManager.register(self)

    def __new__(metaclass, name, bases, attrs, register=True):
        return super().__new__(metaclass, name, bases, attrs)

class AbstractSubHandler(MyHandler, ABC, register=False):
    # not strictly necessary to subclass ABC, but nice to know it won't 
    # screw things up
    @abstractmethod
    def some_method(self):
        pass

try:
    AbstractSubHandler()
except TypeError:
    print("Can not instantiate abstract class")

print(HandlerManager.handlers)
assert AbstractSubHandler not in HandlerManager.handlers
Dunes
  • 37,291
  • 7
  • 81
  • 97
0

I am not sure what you mean by "loaded" classes are usually initialized, or called.

In which case either the __init__ method or the __call__ method are used.

Both can be defined and can include calls to register in a manager.

Classes, and specifically the __init__ method are described better here.


Small example:

class test:
    def __init__(self):
        print 'I have started!'

>>> x = test()
I have started!

(Just replace the print with your registration code.)

Community
  • 1
  • 1
Inbar Rose
  • 41,843
  • 24
  • 85
  • 131
  • 1
    Thanks for answering. I need classes registered before being an instance, which means it is not initialized, the ```__init__``` function is not called. – Albert Tsai Apr 11 '16 at 14:29
  • You need to provide a [mcve] because you are not making any sense. – Inbar Rose Apr 11 '16 at 14:30
0

Yes, there is a way to do this automatically.

As Inbar suggests, the __init__ method is the place to register an object creation.

Here is an example that you can use to effectively wrap existing classes, rather than overwriting __init__. In this case I have made a wrapper for the lists class. By calling super you can use initialising code from the original class.

class nlist(list):
    """ """
    def __init__(self, *args, **kwargs):
        print('making a new list!') # overwrite with call to a manager
        super().__init__(*args)

How this looks:

>>> list('hello')
['h', 'e', 'l', 'l', 'o']
>>> nlist('hello')
making a new list!
['h', 'e', 'l', 'l', 'o']
dodell
  • 470
  • 3
  • 7