typing
typing copied to clipboard
Best Practices document
- Built-in generics
- New union syntax
- No union return types, use
AnyorT | Any - Discuss
Anyvsobject - Callbacks: Use return type
objectinstead ofNoneorAnyif the returned value is ignored
Please add anything you think the document should contain.
What knowledge should the document assume and what are prerequisite documents to read? The typing tutorial + type stub tutorial? If yes then I think this is blocked by those two documents being created first just to build on them.
One practice common in my team is if python 3.7+ is fine (most of our code) then use from future import annotations and feel free to use features in newer version. We're currently on 3.7 and about to migrate to 3.8 before being stuck there for a couple months until 1 dependency updates. If you are in 3.6 you can still use string annotations for new syntax.
Another one is prefer TypedDict over dicts. Sometimes we do need a mapping[str, str] and don't know much about input argument, but a lot of times we do know the structure of dict. I often see people new to types used dict when a specific typeddict is actual expectation. Similarly NamedTuple > namedtuple. Most of the time where mapping is correct its utility functions while main business/api logic has more knowledge of the dict. If you are indexing elements by specific values like d['foo'] then mapping can probably be typeddict instead.
Unsure if this goes in tutorial or best practices, method compatibility check (liskov substituation) is an important one I occasionally see broken. One common reason I see it broken is from superclass really being generic on a type but not stating it.
Include partial/py.typed for any libraries you make. Avoid relying on implicit imports. Specifically the section on what symbols are publicly exported from a module here. Some libraries I use heavily ignore this rule which leads to some type checker confusion. The from x import A as A syntax is one that's pretty uncommon that I usually need to explain.
May fit better in a different doc, but one practice that's helped a lot in improving type coverage of our codebase is adding a CI check that runs type checkers twice on changed files and requires the error count to go down by 5 on every PR compared to master. It's pretty difficult for 1/2 people to add types to large codebase so making a CI rule that pushes in that direction has helped a lot. Any type checker is fine for this and I currently run 2 in CI. Simpler check of requiring 0 type errors would have been unfeasible when a lot files had 100+ type errors (mostly missing types).
There's also question of how to handle recent pep type features that are not supported by all type checkers yet. My current practice is to allow them if some checker handles it and type ignore as needed for other checker (recursive types being one). That's very debatable practice though and it may be better for a best practices doc to avoid type features missing support in a popular checker.
We should include a section about I/O types:
- Use protocols if possible for argument types.
- Use concrete types for return types. Ideally, these derive from
typing.IO. - Use
typing.IO, notio.IOBase.
Came up in python/typeshed#6067.
Another best practice: Use from __future__ import annotations. (Which of course @hmc-cs-mdrissi already mentioned and I overlooked.)