Skip to content

Philosophy

David Foster edited this page Jul 11, 2022 · 9 revisions

The trycast module is primarily designed for recognizing JSON-like structures that can be described by Python's typing system. Secondarily, it can be used for recognizing arbitrary structures that can be described by Python's typing system.

The backbone of this recognition is the trycast() and isassignable() function-pair which can typecheck against arbitrary type annotation objects at runtime. These functions are intended to be:

  • Consistent with typecheckers -- Checking a value against a type T should be successful iff a type checker such as mypy agrees that a function taking a T parameter would accept the value as an argument. Thus, for example, both int and bool values are considered assignable to float because mypy (and PEP 484) say it should be, even though isinstance() disagrees.

  • Non-coercing -- The "parse" operation performed by trycast() will always return the original value upon success and not any other kind of "coerced" or "rounded" value. This is in contrast to libraries like pydantic which may, for example, coerce a str value "5" to 5 when looking for an int.

  • Exhaustively correct -- Checking against a collection type such as list[str] will actually look at every element of a list value to ensure that all are strs. This is in contrast to libraries like beartype which may probabilistically only check a single element in order to provide faster best-effort checking.

  • Decidable -- If you call trycast(T, value) for some fixed T, it will either (1) always return True or False when given a value, or (2) always raise an exception (like TypeNotSupportedException).

  • Standardizable -- Function names use "squishedcase" to align with similar functions in Python's standard library such as isinstance, issubclass, and cast. Following such a convention allows trycast to potentially be integrated into the official typing module at a later date without looking out-of-place.

  • Statically narrowing

    • If isassignable(value, T) returns True than a typechecker should be able to automatically narrow the type of value to T. For example:

      from typing import assert_type  # Python 3.11
      
      def parse(x: object):
          if isassignable(x, str):
              assert_type(x, str)  # type of x is narrowed to str statically
              func_that_expects_str(x)
          else:
              func_that_expects_object(x)
    • The return type of trycast(T, value) should be recognized as T | None by typecheckers. For example:

      from typing import assert_type  # Python 3.11
      
      def parse(x: object):
          if (cleaned_x := trycast(str, x)) is not None:
              assert_type(cleaned_x, str)  # type of cleaned_x is narrowed to str statically
              func_that_expects_str(cleaned_x)
          else:
              func_that_expects_object(x)
    • Achieving the preceding "static narrowing" goals requires that TypeForm support be added to Python's typing system so that both trycast() and isassignable() can be type-annotated in such a way that typecheckers understand what these functions are doing.

Clone this wiki locally