Python Static Typing Ecosystem

repository·main·Indexed 23 days ago

https://github.com/python/typing

Central hub for Python's static typing ecosystem, featuring the official type system specification, documentation, and a conformance test suite for validating static type checkers. Includes guides on providing type annotations for libraries via inline annotations, stub files, or companion packages, and instructions for achieving type completeness using the py.typed marker.

Tokens
82.3K
Snippets
217
Records
330
Agent score
83%

What's inside python-typing

  1. Explore Python typing tutorials, guides, and reference documentation

    main

    The python/python/typing repository provides comprehensive documentation for static typing in Python. You can navigate the following sections to master the type system:

    Tutorials

    • External Libraries: Learn how to apply typing to codebases that use third-party dependencies.

    Guides

    • Libraries: Guidance on using typing within libraries.
    • Writing Stubs: How to create .pyi stub files for type hinting.
    • Modernizing: Strategies for adding type hints to existing code.
    • Unreachable Code: Understanding how the type system identifies unreachable code.
    • Type Narrowing: Techniques for refining types within control flow.
    • Typing Anti-patterns: Common mistakes to avoid.

    Reference

    • Generics: Deep dive into generic programming.
    • Protocols: Using structural subtyping (Protocols).
    • Best Practices: Recommended patterns for clean, type-safe code.
    • Quality: Standards for type-hinting quality.
    • typing Module Documentation: Detailed API reference for the standard library typing module.
  2. Understand the Python Type System Specification

    main
    The Python type system specification is a consolidated document derived from PEP 484 and subsequent typing-related PEPs (following the acceptance of PEP 729). It serves as the authoritative reference for the Python type system, though it is an evolving document that aggregates various PEP sections into a single location.
  3. What is TypeForm and how to use it

    main

    A TypeForm is a special form used in type expressions to represent the runtime value of a type. When a type expression is evaluated at runtime, the resulting value is a type form object.

    TypeForm[T] describes the set of all type form objects that represent the type T or types that are assignable to T.

    Key behaviors:

    • TypeForm[object] describes all valid type form objects.
    • TypeForm[Any] describes a TypeForm whose type argument is not statically known but is a valid type form object. It is assignable both to and from any other TypeForm type.
    • Using TypeForm without a type argument is equivalent to TypeForm[Any].
    from typing import Any, Literal, Optional
    from typing_extensions import TypeForm
    
    ok1: TypeForm[str | None] = str | None  # OK
    ok2: TypeForm[str | None] = str  # OK
    ok3: TypeForm[str | None] = None  # OK
    ok4: TypeForm[str | None] = Literal[None]  # OK
    ok5: TypeForm[str | None] = Optional[str]  # OK
    ok6: TypeForm[str | None] = "str | None"  # OK
    ok7: TypeForm[str | None] = Any  # OK
    
    err1: TypeForm[str | None] = str | int  # Error
    err2: TypeForm[str | None] = list[str | None]  # Error
  4. What is type narrowing and how does it work?

    main

    Type narrowing is a technique where a variable that can take multiple types within a scope is refined to a more specific type based on a runtime conditional check.

    Commonly understood patterns that type checkers use for automatic narrowing include:

    • if x is not None
    • if x
    • if isinstance(x, SomeType)
    • if callable(x)

    Type checkers also typically support narrowing instance attributes (e.g., if x.some_attribute is not None) and sequence members (e.g., if x[0] is not None).

  5. What is a TypedDict

    main

    A TypedDict represents dict objects that contain only string keys. It allows you to specify which keys are valid and what types their associated values must be.

    Key characteristics:

    • Structural Typing: Two TypedDict types are considered equivalent if they have the same structure, even without a common base class.
    • Openness: By default, TypedDict is 'open', meaning it can contain additional keys not explicitly defined. It can also be 'closed' (no extra keys allowed) or have 'extra items' of a specific type.
    • Requirement Levels: Items can be required (must be present) or non-required (can be omitted).
  6. Understand the assignable-to (consistent subtyping) relation

    main

    The assignable-to relation (also known as consistent subtyping) defines when an expression can be assigned to a variable, passed as an argument, or returned from a function.

    A type B is assignable to a type A if there exist fully static materializations A' of A and B' of B such that B' is a subtype of A'.

    Key Behaviors:

    • Relationship to Subtyping: If B is a subtype of A, then tuple[Any, B] is assignable to tuple[int, A]. However, the reverse is not necessarily true.
    • Any and Assignment: Any is assignable to int, and int is assignable to Any.
    • Structural Types: For gradual structural types, assignability is structural. For example, a structural type representing "all objects with an attribute x of type Any" is assignable to a structural type representing "all objects with an attribute x of type int".

    Summary of Type Relations:

    RelationFully Static TypesGradual Types
    SubtypingB is a subtype of AB is assignable to A
    SupertypingA is a supertype of BA is assignable from B
    EquivalenceB is equivalent to AB is consistent with A
  7. Understand protocol assignability rules

    main

    Protocols use structural typing rather than inheritance. Key rules include:

    • Protocols are not instantiable: There are no runtime values whose type is a protocol.
    • Concrete to Protocol: A concrete type X is assignable to protocol P if X implements all members of P with assignable types.
    • Protocol to Protocol: P1 is assignable to P2 if P1 defines all members of P2 with assignable types.
    • Generics: Generic protocols follow standard variance rules.
    • Import Independence: Static type checkers recognize protocol implementations even if the protocol itself is not imported in the consumer module.
  8. Use generic types as base classes

    main

    You can use generic types as base classes for new classes.

    • Built-in generics: Types like list[T], dict[K, V], or Iterable[T] are valid both as types and as base classes.
    • User-defined generics: A class like LinkedList[T] can be used as a base class.
    • Making a class generic: If a base class in the inheritance list uses a type variable, the subclass becomes generic. For example, class MyDict(Mapping[str, T]): makes MyDict a generic class with type parameter T.

    Type Variable Ordering: Type variables are applied to the defined class in the order they first appear in any generic base classes. If you inherit from multiple generic parents, the type checker expects consistent type variable ordering across them.

    from typing import TypeVar
    from collections.abc import Iterable, Container
    
    T = TypeVar('T')
    
    class LinkedList(Iterable[T], Container[T]):
        ...
    
    # LinkedList[int] is now a valid type
  9. Use Unpack for variadic generics compatibility

    main

    To support variadic generics (PEP 646) on older Python versions, the Unpack[] operator can be used as an equivalent to the * operator in index operations and *args annotations.

    • A[*Ts] is equivalent to A[Unpack[Ts]]
    • def f(*args: *Ts): ... is equivalent to def f(*args: Unpack[Ts]): ...
    # Equivalent semantics
    A[Unpack[Ts]]
    
    def f(*args: Unpack[Ts]): ...
  10. Improve type completeness (type coverage)

    main

    Type completeness refers to providing complete and accurate type annotations for all functions, classes, and objects in a library. To increase your type coverage score:

    • Automate checks: Make type completeness an output of your testing process using checker-specific reports.
    • Isolate implementation details:
      • Rename files that are implementation details to start with an underscore (e.g., _internal.py).
      • Rename symbols (functions, classes) that are not part of the public interface to start with an underscore.
    • Manage test/sample code: If your package includes tests or samples, consider removing them from the distribution or placing them in an underscore-prefixed directory so they aren't treated as part of the public interface.
    • Coordinate with dependencies: If you expose types from other libraries, work with those maintainers to ensure they are also fully typed.
  11. How Literal interacts with indexing and getattr

    main

    Literal types allow for "intelligent indexing" into structured data like tuples, NamedTuple, and classes. Type checkers can use the literal value to determine the exact type of the accessed element.

    • Tuples: If you index a tuple with a Literal[int], the type checker can resolve the specific element type.
    • getattr: Using a Literal[str] with getattr allows the type checker to resolve the type of the attribute being accessed.
    a: Literal[0] = 0
    some_tuple: tuple[int, str, list[bool]] = (3, "abc", [True, False])
    
    # The type of some_tuple[a] is inferred as 'int'
    reveal_type(some_tuple[a])
    
    class Test:
        def __init__(self) -> None: self.myfield = 10
    
    t = Test()
    a: Literal["myfield"] = "myfield"
    
    # The type of getattr(t, a) is inferred as 'int'
    reveal_type(getattr(t, a))
  12. Understand the consistency relation for gradual types

    main

    In Python's gradual type system, consistency defines how gradual types relate to one another based on their possible materializations into fully static types.

    • Fully static types: A type A is consistent with B if and only if they are equivalent (A == B).
    • Gradual types: A gradual type A is consistent with B (and vice versa) if there exists a fully static type C that is a materialization of both A and B.
    • Special cases: Any is consistent with every type, and every type is consistent with !Any.

    Key Properties:

    • Symmetric: If A is consistent with B, then B is consistent with A.
    • Reflexive: A is always consistent with A.
    • Not Transitive: Consistency does not guarantee transitivity. For example, tuple[int, int] is consistent with tuple[Any, int], and tuple[Any, int] is consistent with tuple[str, int], but tuple[int, int] is not consistent with tuple[str, int].