{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/annotate-args-and-kwargs/",
  "description": "Properly annotate Python *args and **kwargs with heterogeneous types using Unpack, TypedDict, and modern type hints from PEP-692.",
  "path": "/python/annotate-args-and-kwargs/",
  "publishedAt": "2024-01-08T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python",
    "Typing",
    "TIL"
  ],
  "textContent": "While I tend to avoid args and kwargs in my function signatures, it's not always\npossible to do so without hurting API ergonomics. Especially when you need to write\nfunctions that call other helper functions with the same signature.\n\nTyping args and *kwargs has always been a pain since you couldn't annotate them\nprecisely before. For example, if all the positional and keyword arguments of a function had\nthe same type, you could do this:\n\nThis implies that args is a tuple where all the elements are integers, and kwargs is a\ndictionary where the keys are strings and the values are booleans.\n\nOn the flip side, you couldn't annotate args and *kwargs properly if the values of the\npositional and keyword arguments had different types. In those cases, you'd have to fall\nback to Any, which defeats the purpose.\n\nConsider this example:\n\nHere, the type checker sees each positional argument as a tuple of an integer and a string.\nPlus, it considers each keyword argument as a dictionary where the keys are strings and the\nvalues are either booleans or None.\n\nWith the previous annotation, mypy will reject this:\n\nInstead, it'll accept the following:\n\nYou probably wanted to represent the former while the type checker wants the latter.\n\nTo annotate the second instance correctly, you'll need to leverage bits of [PEP-589],\n[PEP-646], [PEP-655], and [PEP-692]. We'll use Unpack and TypedDict from the typing\nmodule to achieve this. Here's how:\n\nTypedDict was introduced in Python 3.8 to allow you to annotate heterogeneous\ndictionaries. If all the values of a dictionary have the same type, you can simply use\ndict[str, T] to annotate it. However, TypedDict covers the case where all the keys of a\ndictionary are strings but the type of the values varies.\n\nThe following example shows how you might annotate a heterogeneous dictionary:\n\nUnpack marks an object as having been unpacked.\n\nUsing TypedDict with Unpack allows us to communicate with the type checker so that each\npositional and keyword argument isn't mistakenly assumed as a tuple and a dictionary\nrespectively.\n\nWhile the type checker is satisfied when you pass the args and *kwargs as\n\nit'll complain if you don't pass all the keyword arguments:\n\nTo make all of the keywords optional, you could turn off the total flag in the typed-dict\ndefinition:\n\nOr you could mark specific keywords as optional with typing.NotRequired:\n\nThis will let you pass an incomplete set of optional keyword arguments without the type\nchecker yelling at you.\n\nFin!\n\n\n\n\n[pep-589]:\n    https://peps.python.org/pep-0589/\n\n[pep-646]:\n    https://peps.python.org/pep-0646/\n\n[pep-655]:\n    https://peps.python.org/pep-0655/\n\n[pep-692]:\n    https://peps.python.org/pep-0692/",
  "title": "Annotating args and kwargs in Python"
}