pythonoopinheritancepython-3.5factory-pattern

correctly override __new__ in python3


So I am trying to override __new__ and let it exist as a factory to create derived instances. After some reading on SO, I am under the impression that I should be calling __new__ on the derived instance as well.

BaseThing

class BaseThing:
    def __init(self, name, **kwargs):
        self.name = name

    # methods to be derived

ThingFactory

class Thing(BaseThing):
    def __new__(cls, name, **kwargs):
        if name == 'A':
           return A.__new__(name, **kwargs)
        if name == 'B':
           return B.__new__(name, **kwargs)        

    def __init__(self, *args, **kwargs):
        super().__init__(name, **kwargs)

   # methods to be implemented by concrete class (same as those in base)

A

class A(BaseThing):
    def __init__(self, name, **kwargs):
       super().__init__(name, **kwargs)

B

class B(BaseThing):
    def __init__(self, name, **kwargs):
       super().__init__(name, **kwargs)
 

what I am expecting was that it'd just work.

>>> a = Thing('A')

gives me TypeError: object.__new__(X): X is not a type object (str)

I am bit confused by this; when I just return a concrete instance of derived classes, it just worked. i.e.

def __new__(cls, name, **kwargs):
        if name == 'A':
           return A(name)
        if name == 'B':
           return B(name)

I don't think this is the correct way to return in __new__; it may duplicate the calls to __init__.

when I am checking signatures of __new__ in object it seems be this one:

@staticmethod # known case of __new__
def __new__(cls, *more): # known special case of object.__new__
    """ Create and return a new object.  See help(type) for accurate signature. """
    pass

I didn't expect this was the one; I'd expect it came with args and kwargs as well. I must have done something wrong here.

it seems to me that I need to inherit object directly in my base but could anyone explain the correct way of doing it?


Solution

  • You're calling __new__ wrong. If you want your __new__ to create an instance of a subclass, you don't call the subclass's __new__; you call the superclass's __new__ as usual, but pass it the subclass as the first argument:

    instance = super().__new__(A)
    

    I can't guarantee that this will be enough to fix your problems, since the code you've posted wouldn't reproduce the error you claim; it has other problems that would have caused a different error first (infinite recursion). Particularly, if A and B don't really descend from Thing, that needs different handling.