Search code examples
pythoninheritancesuperclassclass-methodpython-class

self.__class__ in parent class' method is missing arguments


A package I am using contains the class A. I want my new class B to inherit from this class. Minimal example:

class A():
    def __init__(self,name):
        self.name = name
    def my_method(self):
        return self.__class__(name = self.name)

class B(A):
    def __init__(self, value, name):
        self.value = value
        super().__init__(name)

B_instance = B(value = 5, name = "Bob")
B_instance.my_method()

Calling the parent's method on the child throws:

File ..., line 5, in my_method
    return self.__class__(name = self.name)
TypeError: __init__() missing 1 required positional argument: 'value'

How can I fix this without changing anything in class A? Many thanks for your help!


Solution

  • A.my_method assumes that A (or any subclass thereof) can be instantiated with a single argument. You broke that assumption by adding a required argument value to B.__init__. You'll need to make value the second parameter, and make it optional.

    class B(A):
        def __init__(self, name, value=None):
            super().__init__(name)
            self.value = value
    

    Whether it is useful to use A.my_method to create an instance of B with value=None is another question.


    Let's pretend you could change A. Then it would be better to design it to facilitate subclassing in the first place.

    class A:
        def __init__(self, *, name, **kwargs):
            super().__init__(**kwargs)
            self.name = name
    
        @classmethod
        def my_method(cls, **kwargs):
            # Note: passing an explicit name argument will
            # result in a TypeError on multiple arguments for
            # the keyword argument 'name'
            return cls(name="Alice", **kwargs)
    

    Now you could define B as

    class B(A):
        def __init__(self, *, value, **kwargs):
            super().__init__(**kwargs)
            self.value = value
    
    
    a1 = A(name="Bob")
    a2 = A.my_method()  # A(name="Alice")
    
    b1 = B(name="Joe", value=5)
    b2 = B.my_method(value=10)  # B(name="Alice", value=10)
    

    This uses advice from https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ with regards to __init__ (and by extension, alternate class-method constructors):

    • use keyword arguments when calling
    • only handle your own parameters explicitly
    • accept all unexpected keyword arguments to pass on to an ancestor class