Search code examples
pythonargumentskeyword-argumentpep-3102

How to add required keyword arguments to a derived class's constructor?


My case is almost the same as this question except I need the keyword arguments to be required.

I want to add required keyword arguments to a derived class, but can't figure out how to go about it. Trying the obvious

class ClassA(some.package.Class):

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

class ClassB(ClassA):

    def __init__(self, *args, a, b, c, **kwargs):
        super(ClassB, self).__init__(*args, **kwargs)
        self.a=a
        self.b=b
        self.c=c

fails because I can't list parameters like that for ClassB's __init__. And

class ClassB(ClassA):

    def __init__(self, *args, **kwargs):
        super(ClassA, self).__init__(*args, **kwargs)
        self.a=a
        self.b=b
        self.c=c

of course doesn't work because the new keywords aren't specified.

How do I add required keyword arguments to the __init__ for a derived class?

Similar question: How do I add keyword arguments to a derived class's constructor in Python?


Solution

  • Don't provide default values, and the keyword arguments are required:

    class ClassB(ClassA):
        def __init__(self, *args, a, b, c, **kwargs):
            super().__init__(*args, **kwargs)  # No need to pass arguments to super in Py3
            self.a = a
            self.b = b
            self.c = c
    

    They're still required to be keyword-only, because they come after the *args argument.

    This feature was added at the same time as keyword-only arguments in PEP 3102, which states:

    Keyword-only arguments are not required to have a default value. Since Python requires that all arguments be bound to a value, and since the only way to bind a value to a keyword-only argument is via keyword, such arguments are therefore 'required keyword' arguments. Such arguments must be supplied by the caller, and they must be supplied via keyword.

    To be clear, keyword-only arguments (required or defaulted) are a Python 3 only feature. If you're trying to do this on Python 2, it's impossible to have Python do the work for you, you just have to perform the checks yourself. Move to Python 3; Python 2 hits end of life (no patches, not even for security) at the end of 2019, so new code should target Python 3 anyway.

    Just for the record, the code you'd use on Python 2 would be something awful like:

    class ClassB(ClassA):
        def __init__(self, *args, **kwargs):
            try:
                self.a = kwargs.pop('a')
                self.b = kwargs.pop('b')
                self.c = kwargs.pop('c')
            except KeyError as e:
                # Convert to TypeError to match Python 3 behavior
                raise TypeError("__init__ missing required keyword-only argument: {!s}".format(e))
            super(ClassB, self).__init__(*args, **kwargs)
    

    Downside is that it doesn't show up in the initializer/class signature, and it's more expensive (in code written and runtime) to manually pop out arguments like this.