Home > Blockchain >  self.__class__ in parent class' method is missing arguments
self.__class__ in parent class' method is missing arguments

Time:07-18

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!

CodePudding user response:

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
  • Related