Skip to content

Instantly share code, notes, and snippets.

@Tatsh
Last active July 13, 2020 18:05
Show Gist options
  • Save Tatsh/edbb01c62128b5eac99d0c0a171a6d51 to your computer and use it in GitHub Desktop.
Save Tatsh/edbb01c62128b5eac99d0c0a171a6d51 to your computer and use it in GitHub Desktop.
Basic typing in Python. Some of this is Mypy-specific.

Mypy

Assertions

Optional values often have to tell the type checker that they are no longer optional. To do this, use assert:

assert x is not None, "x should exist"

Other assertions will work as well, such as isinstance for a Union type:

def func(z: Union[int, str]) -> ...:
    assert isinstance(z, int), "z should be an integer"

For more complex expressions, the assert_non_none function may be useful:

from typing import TypeVar

T = TypeVar('T')

def assert_non_none(x: T, message) -> T:
    if message:
        assert x is not None, message
    else:
        assert x is not None
    return x
    

(assert_non_none(x, "x should not be None") for x in some_iterable_of_optional_values)

{
  assert_non_none(x.a, "x.a should not be None"): assert_non_none(x.b, "x.b should not be None")
  for x in qs
}

Classes vs instances

If you are passing a class (and not an instance) to a function, the function is accepting a Type[...] argument:

def func(cls: Type[SomethingThatCanBeInstantiated]) -> ...: ...

args and kwargs

These cannot be fully typed. You should use Any:

def func(*args: Any, **kwargs: Any) -> ...: ...

Type casting

The cast() function does not actually cast a value at runtime. It is only for type checkers. At runtime it only returns the second argument passed in.

Use generic types

You are encouraged to use generic types rather than very specific types, especially immutable versions. For example, the following is preferred:

def func(it: Sequence[T]) -> ...:
  ...

instead of:

def func(it: List[T]) -> ...:

Note how the above code uses a generic type T.

The code in the first example guarantees it will not be modified (it is immutable).

This only applies to arguments, not return values. Return values can be these, unless the return value is expected to be mutable. You should still prefer immutable return types.

Use the following to help:

  • Prefer Mapping[] over Dict[]
  • Prefer Sequence[] over List[]
    • If you never use len() or index the argument, use Iterable[]

Iterator vs Iterable vs Sequence vs Collection

Generator functions (those that use yield) return Iterator type. The following would be incorrect:

def func() -> Iterable[int]:
  for x in range(1, 3):
    yield x

This passes, but it is better to be more explicit with Iterator.

The function returns a Generator instance, of which Iterator is a subclass.

def func() -> Sequence[int]:  # Can also use Iterable[int]
  collection = []
  for x in range(1, 3):
    collection.append(x)
  return collection

Sequence guarantees ability to index, but Iterable does not. The more generic type would be Iterable and this is okay if the function is returning a type that is not guaranteed to be indexable.

Iterable also does not conform to the Sized protocol. If you must call len() on a sequence, you should specify Sequence or Sized as the type argument.

def func(c: Sized) -> int:
  return len(c)

Collection should be used when you have an iterable that is not a sequence but it is also sized.

def func(c: Collection) -> int:
  len(c)  # ok
  for x in c:  # ok
    ...
  iter(c)  # ok
  x[0]  # error

This section also applies to function arguments.

Missing imports

If you encounter a missing import error with Mypy, and your use of that library is very simple (such as only a few function calls), you are encouraged to create a stub in app/.mypy_stubs.

If the use of the library is too complex (or if the library is too complex), you can add the imports to mypy.ini like so:

[mypy-django_filters.rest_framework]
ignore_missing_imports = True

Note that the import must be complete. Wildcards will not work.

You should look for an existing library installable with Pip before creating stubs or setting ignore_missing_imports.

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment