Gerasimov Mikhail Gerasimov Mikhail - 24 days ago 6
Python Question

Inheritance when __new__() doesn't return instance of class

When

__new__
return instance of class, everything is ok, we can create subclass with no problems:

class A:
def __new__(cls, p1, p2):
self = object.__new__(cls)
return self

def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2

class B(A):
def __new__(cls, p3):
self = super().__new__(cls, 1, 2)
return self

def __init__(self, p3):
super().__init__(1, 2)
self.p3 = p3

a = A(1, 2)
print(a.p2) # output: 2

b = B(3)
print(b.p3) # output: 3


But,


If
__new__()
does not return an instance of
cls
, then the new
instance’s
__init__()
method will not be invoked.


Looks like we have to call
__init__()
inside
__new__()
directly, but this leads to error, when we call
super().__new__
in subclass:

class A:
def __new__(cls, p1, p2):
self = object.__new__(cls)
self.__init__(p1, p2) # we should call __init__ directly
return [self] # return not instance

def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2

class B(A):
def __new__(cls, p3):
self = super().__new__(cls, 1, 2)
return self

def __init__(self, p3):
self.p3 = p3

a = A(1, 2)
print(a[0].p2) # output: 2

b = B(3) # TypeError: __init__() takes 2 positional arguments but 3 were given
print(b[0].p3)


How to resolve it? How to create
A
's subclass if
A.__new__()
doesn't return an instance of class?

Answer

If you are going to call it manually, either don't name the method __init__ and use per-class names instead, or manually call __init__ methods unbound, directly on the class.

Per-class names are relatively easy, you can use double-underscores at the start to produce class-specific names through name mangling:

class A:
    def __new__(cls, p1, p2):
        self = object.__new__(cls)
        self.__init(p1, p2)  # call custom __init directly
        return [self]  # return not instance

    def __init(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

class B(A):
    def __new__(cls, p3):
        self = super().__new__(cls, 1, 2)
        self[0].__init(p3)  # custom init
        return self

    def __init(self, p3):
        self.p3 = p3

or directly on the class:

class A:
    def __new__(cls, p1, p2):
        self = object.__new__(cls)
        A.__init__(self, p1, p2)  # call custom __init__ unbound
        return [self]  # return not instance

    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

class B(A):
    def __new__(cls, p3):
        self = super().__new__(cls, 1, 2)
        B.__init__(self[0], p3)  # call custom __init__ unbound
        return self

    def __init__(self, p3):
        self.p3 = p3

If you are going this way, you may as well do away with a custom initialiser and just do your initialisation in __new__ altogether:

class A:
    def __new__(cls, p1, p2):
        self = object.__new__(cls)
        self.p1 = p1
        self.p2 = p2
        return [self]  # return not instance


class B(A):
    def __new__(cls, p3):
        self = super().__new__(cls, 1, 2)
        self[0].p3 = p3
        return self

After all, you already have access to the instance at creation time, you may as well initialise it there and then.