Search code examples
pythonpython-3.xconstructorsubclassdisjoint-union

Python sum of classes


I want to define a class AorB, such that all A's are AorB's, and all B's are AorB's, and these are all the AorB's. Of course, A and B should be subclasses of AorB. The problem is in AorB.__init__, when I can't convince self it should be something else. I can define an AorB factory, but I'd rather have AorB constructor if possible.

class AorB:
    def __init__(self,par):
        if par: self=A(par) #!
        else: self=B()      #!
    @staticmethod
    def from_par(par):
        if par: return A(par)
        else: return B()
class A(AorB):
    def __init__(self,par):
        self.par=par
class B(AorB):
    def __init__(self):
        pass
print(
    AorB.from_par(5),
    AorB.from_par(0),
    AorB(5),
    AorB(0),
    sep="\n")

I know assignment to self doesn't work here, but I just wanted to show intention. As I said, factory (from_par) works fine, I just want to call it as AorB, not as AorB.from_par.

PS. I know, __init__ is probably too late, type of self is already determined. Feel free to use metaclasses in your answer. It's time I learn something useful about them. :-)


Solution

  • You can't, not with __init__. Once __init__ is being called, the instance is already created.

    You want a factory function instead:

    class AorB: pass
    
    class A(AorB):
        def __init__(self,par):
            self.par=par
    
    class B(AorB):
        def __init__(self):
            pass
    
    def AorB(par):
        return A(par) if par else B()
    

    From an API point of view, there is no difference; AorB is a callable that produces either an A() or a B() instance.

    The other possible route involves defining a __new__ function instead; this is the class constructor:

    class AorB:
        def __new__(cls, par=None):
            if cls is not AorB: return super().__new__(cls)
            return super().__new__(A) if par else super().__new__(B)
    
    class A(AorB):
        def __init__(self, par):
            self.par = par
    
    class B(AorB):
        def __init__(self, par=None):
            pass
    

    Which is just a more involved factory function, really. Note that the __new__ method returns the result of super().__new__ of one of the subclasses based on par, so both A and B will be passed a par parameter, wether they want one or not.

    The if cls is not AorB line is needed to allow instantiating A() or B() directly; you can omit that line if that is not a requirement and only the AorB factory class is used.