Let’s say you have a python function that is used widely throughout your code base. You need to make changes to it. You change the arguments and everything breaks. You painstakingly update all call sites and hope that, when the tests pass, it’s all working as expected.
Is there a better way?
Consider the function
def foo(a, b, c): pass foo(1, 2, 3)
If you want to add another argument to
foo you might update it like so.
def foo(a, b, c, d): pass foo(1, 2, 3, 4)
You need to update all of the call sites because
d is a required positional argument. That’s not too bad because there are a small number of arguments and it’s easy to update by just adding an argument to the end.
Now let’s say you need to update
foo with argument
e which is sometimes
None. Oh, and you realized that
c can also be
def foo(a, b, c, d, e): pass foo(1, 2, None, 4, None)
Now we have several problems:
- Every call site needs to be updated again
- The argument list is getting longer, what is argument 3 again?
- It’s easy to make a mistake updating calls to
foobecause ordering matters—python will happily take
foo(1, 2, 4, 3, 5)
Finally, you want to refactor because the argument list is getting long and they aren’t in a logical order causing frequent instances of problem 3.
def foo(a, b, e, g, c, f, d, h): pass foo(1, 2, None, 7, None, 6, 4, None)
A more extensible way to do this is to use keyword arguments
The ordering of arguments doesn’t matter. If you change the order of arguments in the function, you won’t need to update every call site.
def foo(a, b, e, g, c, f, d, h): pass foo(e=None, a=1, h=None, g=7, c=None, f=6, b=2, d=4)
kwargs with default values can be omitted, perfect for indicating what is actually optional when calling
foo. Python will insist that these arguments go last (making it possible to still reach for calling the function with positional args.
def foo(a, b, d, f, g, c=None, e=None, h=None): pass foo(a=1, b=2, d=4, f=6, g=7)
foo with another optional argument is now super easy—no need to update every call to
foo (unless it needs to use the new optional argument). Notice how the example function call hasn’t changed even though we added argument
i which also altered the argument ordering.
def foo(a, b, d, f, g, c=None, e=None, i=None, h=None): pass foo(a=1, b=2, d=4, f=6, g=7)
To take it one step further, you can require keyword arguments so the function can’t be called with positional arguments.
def foo(*, a, b, d, f, g, c=None, e=None, i=None, h=None): pass # This will throw an error foo(1, 2, 4, 6, 7) # This will not foo(a=1, b=2, d=4, f=6, g=7)