Python allows you to use *args and **kwargs in function definitions to accept an arbitrary number of positional and keyword arguments, respectively.
Here is a simple example:
Different types of function arguments
In the above example, the arbitrary_args
function is defined to accept any number of positional and keyword arguments using the *args
and **kwargs
syntax.
When the function is called with arbitrary_args(1, 2, 3, a="apple", b="banana")
, the *args
collects all the positional arguments (1, 2, 3) into a tuple, and **kwargs
gathers the keyword arguments (a and b) into a dictionary.
Consequently, the function prints the tuple (1, 2, 3)
and the dictionary {'a': 'apple', 'b': 'banana'}
, demonstrating the dynamic nature of these special argument types.
The so-called ‘arbitrary argument list’ (often abbreviated to ‘args’) allows functions to accept any number of positional arguments, while the ‘arbitrary keyword argument dictionary’ (often shortened to ‘kwargs’) allows for any number of keyword arguments.
For a more in-depth explanation see The Hitchhiker’s Guide to Python and our related Python tip.
Examples from Django
Args and kwargs are used quite a lot in Python. For example, just looking at Django’s source code you’ll find:
def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
and:def redirect(to, *args, permanent=False, **kwargs):
and:def get_object_or_404(klass, *args, **kwargs):
Possible downsides of args and kwargs
The versatility of this is great, but there are also some downsides to keep in mind 💡
1. This leads to less readable code and self documentation of your functions 🤔
2. They lead to lack of type checking and IDE support (code suggestions and auto-completion) 😱
3. It will possibly be harder to maintain over time: any changes in the order of arguments or function signature may result in unintended behavior (see an example here).
Essentially we’re opening up the flood gates, allowing an arbitrary number of arguments to go into our functions 😅
The Trade-off: Flexibility vs. Explicitness
The Zen of Python’s principle “explicit is better than implicit“, which we also discussed here, encourages developers to be clear and unambiguous in their code.
Using *args and **kwargs can introduce ambiguity, as the function’s signature does not clearly define the names and types of the expected arguments.
While *args and **kwargs can be valuable in certain situations, it’s essential to use them judiciously and thoughtfully.
Explicitly defining function signatures with descriptive argument names and proper type annotations enhances code readability, maintainability, and reduces the likelihood of introducing subtle bugs.
It’s a balance between the flexibility offered by *args and **kwargs and the clarity provided by explicit argument definitions.
One use case where you really need this flexibility for example is decorators (article / tip).
Conclusion
In conclusion, while Python’s *args
and **kwargs
provide a dynamic and flexible way to handle function arguments, their use comes with trade-offs. Striking the right balance is key.
Being explicit in our code fosters readability, maintainability, and reduces potential pitfalls. As developers, it’s our responsibility to weigh the benefits of flexibility against the clarity and safety of explicitness.
As always, understanding our tools and using them judiciously is important to writing robust and understandable code.
These insights often emerge from code reviews in real-world, complex applications. We simulate this real developer setting in our PDI and PDM programs. Check them out if you’re serious about taking your Python developer (and mindset) skills to the next level …