Differences between revisions 2 and 4 (spanning 2 versions)
Revision 2 as of 2023-01-06 18:57:38
Size: 2427
Comment:
Revision 4 as of 2023-01-07 00:59:04
Size: 7118
Comment:
Deletions are marked like this. Additions are marked like this.
Line 15: Line 15:
'''`Any`''' satisfies all type checking.

For functions that should never return or should never be called, use '''`Never`'''.


=== Any ===

'''`Any`''' satisfies all type checking, which can be useful for making things 'just work'.



=== Callable ===

Do not use `typing.Callable[...]` in Python 3.9+; instead use `collections.abc.Callable[...]`.



=== Literal ===

To annotate an argument or return value that must be one of a set of literal values, use '''`Literal`'''.

{{{
from typing import Literal

def true() -> Literal[True]:
    return True

def open_helper(file: str, mode: Literal['r', 'rb', 'w', 'wb']) -> None:
    pass
}}}



=== LiteralString ===

To annotate a string argument or return value that must one of...

 * a literal string value
 * a string value annotated as `Literal` or `LiteralString`
 * a combination of the above

...use '''`LiteralString`'''. As an example, consider SQL templating functions.

{{{
from typing import LiteralString

def run_query(sql: LiteralString) -> None
    pass

def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
    # pass, because is a literal string
    run_query("SELECT * FROM students")

    # pass, because is a value annotated as a LiteralString
    run_query(literal_string)

    # pass, because is a combination of a literal string and a value annotated as a LiteralString
    run_query("SELECT * FROM " + literal_string)

    run_query(arbitrary_string) # type checker error
    run_query( # type checker error
        f"SELECT * FROM students WHERE name = {arbitrary_string}"
    )
}}}

This adds a degree of safety by way of the type checker.

The following `str` methods all preserve `LiteralString` annotation.

 * `capitalize`
 * `casefold`
 * `center`
 * `expandtabs`
 * `format`
 * `join`
 * `ljust`
 * `lower`
 * `lstrip`
 * `partition`
 * `removeprefix`
 * `removesuffix`
 * `replace`
 * `rjust`
 * `rpartition`
 * `rsplit`
 * `rstrip`
 * `split`
 * `splitlines`
 * `strip`
 * `swapcase`
 * `title`
 * `upper`
 * `zfill`
 * `__add__`
 * `__iter__`
 * `__mod__`
 * `__mul__`
 * `__repr__`
 * `__rmul__`
 * `__str__`
 * [[Python/FStrings|f-strings]]



=== Optional ===

To annotate an argument that can also be `None`, use '''`Optional`'''.

{{{
from typing import Optional

def int_or_none(arg: Optional[int]) -> None:
    if arg is not None:
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1
}}}



=== Tuple ===

Do not use `typing.Tuple[...]` in Python 3.9+; instead use the built-in `tuple[...]`.



=== Union ===

To annotate an argument that can be multiple types, use '''`Union`'''.

{{{
from typing import Union

def int_or_str(arg: Union[int, str]) -> None:
    if isinstance(arg, int):
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1
    else:
        # type checkers also infer that `arg` is a `str` here
        arg += "1"
}}}

If the argument can be either a type or `None`, consider `Optional`.

See also the [[Python/Builtins#Operators|union operator]] (`|`).


----



== Annotations for Type Hints ==

The following annotations are mostly useful for visual hints in IDEs.



=== Annotated ===

{{{
from typing import Annotated

SmallInt = Annotated[int, ValueRange(0, 9)]
}}}



----



== Annotations for Annotations ==

The following annotations are mostly useful for supporting annotations.



=== TypeGuard ===

Functions like `isinstance` function as a type guard; subsequent conditional lines are checked knowing that a value has passed the guard and is of a contrained type.

Custom type guard functions can be written and annotated with '''`TypeGuard`'''. If the function returns `True`, then the corresponding value is of a constrained type.

{{{
from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)
}}}

----



== Annotations for Logic Checks ==

The following annotations are mostly useful for causing a type checker to identify issues with program logic. They may be necessary to satisfy the type checker in edge cases.



=== Functions ===



==== Never ====

If there is a function that should never be called, annotate the argument with '''`Never`'''.
Line 40: Line 240:
For class methods that return an instance of the class, use '''`Self`'''.

==== NoReturn ====

Do not use `NoReturn` in Python 3.11+; instead use `Never`.



=== Constants ===



==== Final ====

If there is a constant that should never be changed, annotate with '''`Final`'''.

{{{
from typing import Final

MAX_CONN: Final[int] = 1

# fail
MAX_CONN += 1
}}}



=== Classes ===



==== ClassVar ====

If there is a class attribute that should never be set on an instance, annotate with '''`ClassVar`'''.

{{{
from typing import ClassVar

class Quadruped:
    feet: ClassVar[int] = 4

cat = Quadruped("cat")

# fail
cat.feet = 3
}}}



==== Self ====

For class methods that return an instance of the class, use '''`Self`'''. This is superior to using forward references because subclasses will automatically have the correct annotation.
Line 50: Line 301:


=== Deprecations ===

Do not use `typing.Tuple[...]` in Python 3.9+; instead use the built-in `tuple[...]`.

Do not use `typing.Callable[...]` in Python 3.9+; instead use `collections.abc.Callable[...]`.

Do not use `NoReturn` in Python 3.11+; instead use `Never`.
Line 104: Line 345:


Python Typing

The typing and typing_extensions modules support the type annotation system in the Python language.

The type annotation system has evolved rapidly with multiple instances of breaking changes. For an overview of the entire system, see Type Annotation.


Primitive Types

Any

Any satisfies all type checking, which can be useful for making things 'just work'.

Callable

Do not use typing.Callable[...] in Python 3.9+; instead use collections.abc.Callable[...].

Literal

To annotate an argument or return value that must be one of a set of literal values, use Literal.

from typing import Literal

def true() -> Literal[True]:
    return True

def open_helper(file: str, mode: Literal['r', 'rb', 'w', 'wb']) -> None:
    pass

LiteralString

To annotate a string argument or return value that must one of...

  • a literal string value
  • a string value annotated as Literal or LiteralString

  • a combination of the above

...use LiteralString. As an example, consider SQL templating functions.

from typing import LiteralString

def run_query(sql: LiteralString) -> None
    pass

def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
    # pass, because is a literal string
    run_query("SELECT * FROM students")

    # pass, because is a value annotated as a LiteralString
    run_query(literal_string)

    # pass, because is a combination of a literal string and a value annotated as a LiteralString
    run_query("SELECT * FROM " + literal_string)

    run_query(arbitrary_string)  # type checker error
    run_query(  # type checker error
        f"SELECT * FROM students WHERE name = {arbitrary_string}"
    )

This adds a degree of safety by way of the type checker.

The following str methods all preserve LiteralString annotation.

  • capitalize

  • casefold

  • center

  • expandtabs

  • format

  • join

  • ljust

  • lower

  • lstrip

  • partition

  • removeprefix

  • removesuffix

  • replace

  • rjust

  • rpartition

  • rsplit

  • rstrip

  • split

  • splitlines

  • strip

  • swapcase

  • title

  • upper

  • zfill

  • __add__

  • __iter__

  • __mod__

  • __mul__

  • __repr__

  • __rmul__

  • __str__

  • f-strings

Optional

To annotate an argument that can also be None, use Optional.

from typing import Optional

def int_or_none(arg: Optional[int]) -> None:
    if arg is not None:
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1

Tuple

Do not use typing.Tuple[...] in Python 3.9+; instead use the built-in tuple[...].

Union

To annotate an argument that can be multiple types, use Union.

from typing import Union

def int_or_str(arg: Union[int, str]) -> None:
    if isinstance(arg, int):
        # type checkers understand type guards; `arg` is checked as an `int` here
        arg += 1
    else:
        # type checkers also infer that `arg` is a `str` here
        arg += "1"

If the argument can be either a type or None, consider Optional.

See also the union operator (|).


Annotations for Type Hints

The following annotations are mostly useful for visual hints in IDEs.

Annotated

from typing import Annotated

SmallInt = Annotated[int, ValueRange(0, 9)]


Annotations for Annotations

The following annotations are mostly useful for supporting annotations.

TypeGuard

Functions like isinstance function as a type guard; subsequent conditional lines are checked knowing that a value has passed the guard and is of a contrained type.

Custom type guard functions can be written and annotated with TypeGuard. If the function returns True, then the corresponding value is of a constrained type.

from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)


Annotations for Logic Checks

The following annotations are mostly useful for causing a type checker to identify issues with program logic. They may be necessary to satisfy the type checker in edge cases.

Functions

Never

If there is a function that should never be called, annotate the argument with Never.

from typing import Never

def never_call_me(arg: Never) -> None:
    pass

# fail
def do_it_anyway(arg: int | str) -> None:
    never_call_me(arg)

# pass: arg is either int or str so the default case is never reached
def int_or_str(arg: int | str) -> None:
    match arg:
        case int():
            pass
        case str():
            pass
        case _:
            never_call_me(arg)

NoReturn

Do not use NoReturn in Python 3.11+; instead use Never.

Constants

Final

If there is a constant that should never be changed, annotate with Final.

from typing import Final

MAX_CONN: Final[int] = 1

# fail
MAX_CONN += 1

Classes

ClassVar

If there is a class attribute that should never be set on an instance, annotate with ClassVar.

from typing import ClassVar

class Quadruped:
    feet: ClassVar[int] = 4

cat = Quadruped("cat")

# fail
cat.feet = 3

Self

For class methods that return an instance of the class, use Self. This is superior to using forward references because subclasses will automatically have the correct annotation.

from typing import Self

class Foo:
    def return_self(self) -> Self:
        return self


Custom Types

To create a custom type, use NewType. Type checkers will treat the custom type as a subclass of the original type, while rejecting passed arguments of the original type.

from typing import NewType

UserId = NewType('UserId', int)

# passes
user_a = get_user_name(UserId(42351))

# fails
user_b = get_user_name(-1)

Given the above example, at runtime, UserId returns a callable that immediately returns the original value. This leads to three properties:

  1. UserId(value) has minimal runtime overhead

  2. an instance of UserId cannot be returned at runtime; UserId(value) immediately evaluates to value

  3. UserId is not subclassable (except by chaining NewType: ProUserId = NewType('ProUserId', UserId))


Generics

To annotate a generic type, use TypeVar.

from collections.abc import Sequence
from typing import TypeVar

T = TypeVar('T')

def first(l: Sequence[T]) -> T:
    return l[0]


CategoryRicottone

Python/Typing (last edited 2023-04-20 14:07:06 by DominicRicottone)