Search code examples
pythonkeyword-argument

Class methods in python: default kwargs from attributes of self


Is it possible to define a class method which takes an attribute of self as a default keyword argument? i.e. I'd like to do something equivalent to the following:

class Foo:
    def __init__(self, bar):
        self.bar = bar

    def do_a_thing(self, my_arg=self.bar):
        do_something_else(my_arg)

Of course this throws an error:

x = Foo(y)
x.do_a_thing()

NameError: name 'self' is not defined

Hopefully it is clear what kind of behavior I am hoping to get. Calling x.do_a_thing() should be equivalent to x.do_a_thing(my_arg=x.bar).

Is there a way to do this?


Solution

  • Whenever you want a default value that can't easily be specified statically, the idiom is to use a sentinel value.

    If None is not a valid value that anyone might ever pass, use that:

    def do_a_thing(self, my_arg=None):
        if my_arg is None:
            my_arg = self.bar
        do_something_else(my_arg)
    

    Or, if there's no falsey value that would ever be valid:

    def do_a_thing(self, my_arg=None):
        if not my_arg:
            my_arg = self.bar
        do_something_else(my_arg)
    

    Or, if even None could be a value, you need to create a private sentinel that nobody will ever pass:

    _sentinel = object()
    def do_a_thing(self, my_arg=_sentinel):
        if my_arg is _sentinel:
            my_arg = self.bar
        do_something_else(my_arg)
    

    By the way, calling this a "default keyword argument" implies a serious misunderstanding. The fact that the syntax for default values in function definitions looks similar to the syntax for keyword arguments in function calls is a coincidence. In particular, this is not a keyword parameter, and it can be called without using keyword arguments. For example:

    my_thing.do_a_thing(20)
    

    If you actually want a keyword-only parameter, it has to come after a * (or after *args, if you have one of those). And, because these are completely unrelated features, you can have keyword-only parameters without default values:

    def do_a_thing(self, *, my_arg):
        do_something_else(my_arg)
    

    And "kwargs" usually isn't used as shorthand for "keyword argument" or "keyword parameter", but rather for the **kwargs parameter that gathers all unknown keyword arguments.