{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/type-guard/",
  "description": "Use Python's TypeGuard to create custom type narrowing functions that help static type checkers understand runtime type checks and validations.",
  "path": "/python/type-guard/",
  "publishedAt": "2022-02-23T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python",
    "Typing"
  ],
  "textContent": "Static type checkers like Mypy follow your code flow and statically try to figure out the\ntypes of the variables without you having to explicitly annotate inline expressions. For\nexample:\n\nThe reveal_type function is provided by Mypy and you don't need to import this. But\nremember to remove the function before executing the snippet. Otherwise, Python will raise a\nruntime error as the function is only understood by Mypy. If you run Mypy against this\nsnippet, it'll print the following lines:\n\nHere, I didn't have to explicitly tell the type checker how the conditionals narrow the\ntypes.\n\n> Static type checkers commonly employ a technique called 'type narrowing' to determine a\n> more precise type of an expression within a program's code flow. When type narrowing is\n> applied within a block of code based on a conditional code flow statement (such as if and\n> while statements), the conditional expression is sometimes referred to as a 'type guard'.\n> -- PEP-647\n\nSo, in the above snippet, Mypy performed type narrowing to determine the more precise\ntype of the variable x; and the if ... else conditionals, in this case, is known as\ntype guards.\n\nHowever, when the type checker encounters a complex expression, often time, it can't figure\nout the types statically. Mypy will complain when it faces one of these issues:\n\nHere, the check_sequence_str checks whether the input argument is a sequence of strings in\nruntime. Then in the concat function, I used it to check whether the input conforms to the\nexpected type requirement; if it does, the function performs string concatenation on the\ninput and returns the value. Otherwise, it returns None. If you run mypy against this,\nit'll complain:\n\nThe type checker can't figure out that the container type is list[str].\n\nFunctions like check_sequence_str that - checks the type of an input object and returns a\nboolean - are called type guard functions. PEP-647 proposed a TypeGuard class to help\nthe type checkers to narrow down types from more complex expressions. Python 3.10 added the\nTypeGuard class to the typing module. You can use it like this:\n\nNotice that the return type now has the expected type defined inside the TypeGuard\ngeneric. Now, Mypy will be satisfied if you run it against the modified snippet.\n\nProperties of type guard functions\n\nYou've already seen how check_sequence_str narrows down the type of an object in runtime.\nFunctions like this can be loosely called user-defined type guard functions. However, to be\nconsidered a proper type guard function by the type checker, the callable needs to pass\nthrough a few more checks.\n\n> When TypeGuard is used to annotate the return type of a function or method that accepts at\n> least one parameter, that function or method is treated by type checkers as a user-defined\n> type guard. The type argument provided for TypeGuard indicates the type that has been\n> validated by the function. - PEP-647\n\n- Usually, a type guard function only takes a single parameter and returns a boolean value\n  based on the conformity of the type of the incoming object with the expected type. The\n  expected type needs to be wrapped in TypeGuard and added as the return type annotation.\n\n- Type checkers will only check if the first positional argument conforms to the expected\n  return type annotation. It'll ignore other parameters if there is more than one.\n\n- If you define a type guard callable in a class, in that case, the type checker will ignore\n  self/cls argument and check the second positional parameter for type conformity.\n  Additional parameters won't be checked.\n\n- The input type is usually wider than the output type. In our example case, the input type\n  Sequence[object] is less specific than that of the return type Sequence[str]. However,\n  this is mostly a convention and not enforced by any means.\n\n> The return type of a user-defined type guard function will normally refer to a type that\n> is strictly \"narrower\" than the type of the first argument (that is, it's a more specific\n> type that can be assigned to the more general type). However, it is not required that the\n> return type be strictly narrower. - PEP-647\n\nGeneric type guard functions\n\nUser-defined type guards can be generic functions, as shown in this example:\n\nHere, type guard function list_of_t is a generic function since it accepts a generic\ncontainer Sequence[T]. The first parameter is the input type that the type checker will\nfocus on, and the second parameter denotes the default types that are allowed inside the\noutput list. Running the snippet will print jupiter and mars in the console and mypy\nwill also be happy with the types.\n\nFurther reading\n\n- [PEP 647 - User-defined type guards]\n- [Python type hints - how to narrow types with TypeGuard]\n\n\n\n\n[pep 647 - user-defined type guards]:\n    https://www.python.org/dev/peps/pep-0647/\n\n[python type hints - how to narrow types with typeguard]:\n    https://adamj.eu/tech/2021/06/09/python-type-hints-how-to-narrow-types-with-typeguard/",
  "title": "Narrowing types with TypeGuard in Python"
}