0

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. :-)

Veky
  • 2,646
  • 1
  • 21
  • 30

1 Answers1

3

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • "Factory function" is not good enough for me. As far as I can see, you just overwrote class AorB with def AorB. So isinstance(AorB(5),AorB) raises TypeError. :-( And I suspected `__init__` is too late. Can `__new__` and metaclasses be of any help? – Veky Jun 24 '13 at 23:47
  • @user1875565: Yes, but it's more involved and ugly. – Martijn Pieters Jun 24 '13 at 23:52
  • Uglyness is in the eye of beholder. :-) One more question: can `_constructing` be avoided by having A and B their own `__new__` that calls `super().__new__`? Also, I'd rather not use default parameters, the real situation is more complicated and A and B constructors already use their fair share of `None`s. :-) – Veky Jun 25 '13 at 00:06
  • By testing for the exact type of the `cls` argument we can; I can't really test right now though. – Martijn Pieters Jun 25 '13 at 00:42