understanding python *args and **kwargs

edit ✏️


Python *args and **kwargs are super useful tools, that when used properly can make you code much simpler and easier to maintain. Large manual conversions from a dataset to function arguments can be packed and unpacked into lists or dictionaries. Beware though, this power can lead to some really unreadable/unusable code if done wrong.

*args are for lists

*args are some magical syntax that will collect function arguments into a list, or unpack a list into individual arguments.

recieving *args

When recieving variables as a *<varname> , commonly *args , the arguments get packed into an ordered list.

Never add *args to your function definition (almost never)

Generally I find *args poor naming and it only drives confusion to the user looking at your function trying to decide what exactly it does. Here I have chosen the name printrows since we are printing each item as a row.

def printer(*printrows: str) -> None:
  for i, row in enumerate(printrows):
    print(i, row)
>>> printer('eggs', 'spam', 'ham')
0 eggs
1 spam
2 ham

Be Aware of AntiPatterns

If your *args collection is distictly different things, then make them separate variables. Using *args as a crutch can lead to a really confusing api for your users, even yourself.

Here *args is confusing as we are a bit unsure of what to pass to get_user_data , or which order it needs to be in without reading the code.

def get_user_data(*args):
  "does stuff given a users GitHub and DevTo username"
  github = reuqests.get(f'https://api.github.com/users/{args[0]}')
  devto = requests.get(f'https://dev.to/api/users/by_username?url={args[1]}')

Here the function signature makes it clear what get_user_data expects. Users will not have to read your docstring or worse your source code to understand it each time the reference it.

def get_user_data(github_username, devto_username):
  "does stuff given a users GitHub and DevTo username"
  github = reuqests.get(f'https://api.github.com/users/{github_username}')
  devto = requests.get(f'https://dev.to/api/users/by_username?url={devto_username}')

sending *args

Inversely we can send a list of things as individual arguments by unpacking them into the function call.

>>> things_to_print = ['eggs', 'spam', 'ham']
>>> printer(*things_to_print)
0 eggs
1 spam
2 ham

**kwargs are for dictionaries

Just like *args being for lists, **kwargs are for dictionaries. When packing them up inside of a function. The argument name passed in becomes the key, then invers happens when unpacking, the key becomes the argument for the function.

recieving **kwargs

Here is a function accepting **printrows as it's only input. Any keyword argument that you pass into the function will get packed into a dictionary.

def printer(**printrows: str) -> None:
  for key in printrows:
    print(key, printrows[key])
>>> printer(breakfast='eggs', lunch='spam', dinner='ham')
breakfast eggs
lunch spam
dinner ham

Any arguments passed in will throw a TypeError , since this printer does not accept any positional arguments.

TypeError                                 Traceback (most recent call last)
<ipython-input-2-f03e96cb5e14> in <module>
----> 1 printer("one")

TypeError: printer() takes 0 positional arguments but 1 was given

Avoid Anti-Patterns

Just as above, if your items are clearly separate things, make them separate things and do not use **kwargs . **kwargs are great when you have collections of things that all get treated exactly the sam, if they get treated differently, or you are expecting certain keys to always exist it will be very confusing to your users what they need to pass in.

sending **kwargs

Sending **kwargs is quite useful. Especially when combining various libraries together. Often times you can coerse objects into a dictionary, often with something like .to_dict() , then pass that whole dictionary to another function. This makes gluing different libraries together a breeze at times.

>>> things_to_print = {breakfast:'eggs', lunch:'spam', dinner:'ham'}
>>> printer(**things_to_print)
breakfast eggs
lunch spam
dinner ham

I setup a replit.com with these examples so that you can quickly jump in, run it, break it, fix it, add breakpoints and really get a feel for them yourself. Check it out 👉 https://replit.com/@WaylonWalker/args#main.py

I hope this helps you understand *args and **kwargs just a bit more. They can be quite handy to greatly simplify repetative code, expecially if we already have the data setup in the right data structure.