Search code examples
pythondecoratorsubclassingclass-method

Decorated classes inheritance and classmethods


How to decorate a class in the right way to be able to inherit a classes from the decorated one? Is there exists a right way?

If I will do something like:

def singleton(cls):                            
    inst = {}                                  
    def get(*args, **kwargs):                  
        cls_id = id(cls)                       
        if cls_id not in inst:                 
            inst[cls_id] = cls(*args, **kwargs)
        return inst[cls_id]                    
    return get 


@singleton
class A(object):
    @classmethod
    def cls_meth(cls):
        return cls                             

I have no chance to inherit a class from above one, because before I call it this is the function. The same problem with classmethods, function have no classmethods.

class B(A): # No way! `A` is the function!
    pass

A.cls_meth() # AttributeError: 'function' object has no attribute 'cls_meth'    

Even I doing something like:

class SingletonDecorator(object):                        
    inst = {}                                            
    def __init__(self, cls):                             
        self.cls = cls                                   

    def __call__(self, *args, **kwargs):                 
        cls_id = id(self.cls)                            
        if cls_id not in self.inst:                      
            self.inst[cls_id] = self.cls(*args, **kwargs)
        return self.inst[cls_id]                         


@SingletonDecorator
class A(object):
    pass

When I inherit a class from A class, it will be inherited from SingletonDecorator class instead of A.

class B(A): # No no no! I do not need a `SingletonDecorator` child class...
    pass

There is a way to modify class instance via __metaclass__, but but that is absolutely another story...


Solution

  • Your decorator needs to return the original class (or a subclass of it) to be able to subclass it further. Your decorator returns an instance of itself when used as a decorator (because that's the default behavior of calling a class).

    For example, something like this:

    def singleton(cls):
        class C(cls):
            _instance = None
            def __new__(c, *args, **kwargs):
                if type(c._instance) != c:
                    c._instance = cls.__new__(c, *args, **kwargs)
                return c._instance
        C.__name__ = cls.__name__
        return C
    

    This returns a subclass of the original class with an overridden __new__() method that does the singleton magic. This class is subclassable.

    There is almost never any reason to create a singleton, however. Often it's just a way of hiding global state that would be better eliminated.