DEV Community

Super Kai (Kazuya Ito)
Super Kai (Kazuya Ito)

Posted on

Memo for type hints-related posts in Python (1)

There are a supertype-like type and subtype-like type:

  • I call a type a supertype-like type if the type isn't actually a supertype of other type but the type accepts the other type like the type is a supertype of the other type. For example, complex isn't actually a supertype of float, int and bool but complex accepts them like complex is a supertype of them so complex is a supertype-like type of float, int and bool.
  • I call a type a subtype-like type if the type isn't actually a subtype of other type but the type is accepted by the other type like the type is a subtype of the other type. For example, bool isn't actually a subtype of complex and float but bool is accepted by them like bool is a subtype of them so bool is a subtype-like type of complex and float.

The new syntax of generics is name[parameter, ...] which can be used from Python 3.12:

  • name(Required):
    • It's an identifier.
  • [parameter, ...](Required):
    • A parameter is a (generic) type parameter.
    • parameter must be at least one so the empty type parameter [] cannot be used.
    • For a parameter, a positional type argument can be set but a keyword type argument cannot be set.
    • parameter is */**name:bound/constraints=default:
      • */**(Optional):
        • It cannot be both * and **.
        • Without */**, parameter is TypeVar base.
        • With *, parameter is TypeVarTuple base.
        • With **, parameter is ParamSpec base.
        • The old syntax TypeVar, TypeVarTuple and ParamSpec can still be used.
        • For TypeVar base, variance is automatically inferred.
        • For TypeVarTuple and ParamSpec base, variance is always invariant according to the doc but they can be covariant and contravariant, and contravariant according to the issue and the issue respectively.
      • name(Required):
        • It's an identifier.
      • :bound/constraints(Optional):
        • It cannot be both bound and constraints.
        • It isn't supported for TypeVarTuple and ParamSpec base by Python interpreter and type checkers.
        • bound(Type:Type):
          • For TypeVar base:
            • It's a type.
            • The type, its subtype and subtype-like type can be accepted as type arguments.
        • constraints(Type:tuple(Type)):
          • For TypeVar:
            • It must be at least two types.
            • The types can be but their subtypes and subtype-like types cannot be accepted as type arguments.
      • =default(Optional):
        • default(Type:Type):
          • For TypeVar:
            • It's a default type.
            • If constraints is set, it must be one type of constraints but not their subtype or subtype-like type.
            • If bound is set, it must be the type, subtype or subtype-like type of bound.
          • For TypeVarTuple base:
            • It's a default unpacked tuple.
            • It doesn't work according to the issue.
          • For ParamSpec:
            • It's a list of default types.
            • If bound is set, it must be the types, subtypes and subtype-like types of bound in a list.

The old syntax of generics is TypeVar, TypeVarTuple and ParamSpec:

  • For TypeVar:
    • The 1st parameter is name(Required-Type:str):
      • It's an identifier.
      • It must be the same name as its variable.
      • In convention, T.* is used like T, T1, T2, etc.
    • The 2nd parameter is *constraints(Optional-Type:Type):
      • It's two or more types.
      • The types can be but their subtypes and subtype-like types cannot be accepted as type arguments.
      • Don't use any keywords like *constraints=, constraints=, etc.
    • The 3rd parameter is bound(Optional-Default:None-Type:Type):
      • It's a type.
      • The type, its subtype and subtype-like type can be accepted as type arguments.
    • The 4th parameter is covariant(Optional-Default:False-Type:bool).
    • The 5th parameter is contravariant(Optional-Default:False-Type:bool).
    • The 6th parameter is infer_variance(Optional-Default:False-Type:bool).
    • The 7th parameter is default(Optional-Default:typing.NoDefault-Type:Type):
      • It's a default type.
      • If *constraints is set, it must be one type of *constraints but not their subtype or subtype-like type.
      • If bound is set, it must be the type, subtype or subtype-like type of bound.
    • Only one of *constraints or bound can be set but not two.
    • By default, variance is invariant.
    • Only one of covariant, contravariant or infer_variance can be set but not more than one.
    • mypy doesn't support infer_variance according to the issue.
  • For TypeVarTuple:
    • The 1st parameter is name(Required-Type:str):
      • It's an identifier.
      • It must be the same name as its variable.
      • In convention, Ts.* is used like Ts, Ts1, Ts2, etc.
    • The 2nd parameter is default(Optional-Default:typing.NoDefault-Type:Type):
      • It's a default unpacked tuple:
      • It doesn't work according to the issue.
    • Variance is always invariant according to the doc but it can be covariant and contravariant according to the issue.
  • For ParamSpec:
    • The 1st parameter is name(Required-Type:str):
      • It's an identifier.
      • It must be the same name as its variable.
      • In convention, P.* is used like P, P1, P2, etc.
    • The 2nd parameter is bound(Optional-Default:None-Type:Type) (Not supported):
      • It's a list of types.
      • The types in a list, their subtypes and subtype-like types can be accepted as type arguments.
    • The 3rd parameter is covariant(Optional-Default:False-Type:bool) (Not supported).
    • The 4th parameter is contravariant(Optional-Default:False-Type:bool) (Not supported).
    • The 5th parameter is default(Optional-Default:typing.NoDefault-Type:Type):
      • It's a list of default types.
      • If bound is set, it must be the types, subtypes and subtype-like types of bound in a list.
    • Type checkers don't support bound, covariant and contravariant.
    • Variance is always invariant according to the doc but it can be contravariant according to the issue.
    • With mypy, using name keyword argument gets error, which is a bug according to the issue.

The generic type syntax of generics is type[argument, ...] for both the new and old syntax of generics:

  • type(Required-Type:Type):
    • It's a type.
  • [argument, ...](Optional):
    • An argument is a (generic) type argument.
    • argument must be at least one so the empty type argument [] cannot be used.
    • An argument must a positional type argument but not a keyword type argument.
    • It can be omitted as long as:
      • all the type parameters have default types whether mypy is run with or without --strict.
      • or mypy is run without --strict whether all the type parameters have default types or not:
        • Any is set to the type parameters without default types.
    • argument(Required-Type:Type):
      • It's a type.

Variance:

  • is the feature of generics to decide whether a type accepts its supertypes, supertype-like types, subtypes and subtype-like types in addition to the type:
  • has 3 kinds invariance, covariance and contravariance:
    • Invariance only makes a type accept the type.
    • Covariance makes a type accept the type, its subtypes and subtype-like types.
    • Contravariance make a type accept the type, its supertypes and supertype-like types.
  • is inferred for only TypeVar (base) in the new and old syntax of generics:
    • In the old syntax, variance inference happens if infer_variance=True is set to TypeVar:
      • mypy doesn't support infer_variance according to the issue.
    • TypeVarTuple and ParamSpec (base) are always invariant according to the doc but they can be covariant and contravariant, and contravariant according to the issue and the issue respectively.
  • This is how variance is inferred:
    • Covariant if the variable in a class can be read but cannot be written from outside the class.
    • Contravariant if the variable in a class can be written but cannot be read from outside the class.
    • Invariant if the variable in a class can be read and written from outside the class.
Inferred Variance Reason Example
Covariant Read-Only tuple
Contravariant Write-Only Callable
Invariant Read-Write list

Top comments (0)