From 08cffdbb0bc33acbe6896121a58ffe4e0c68b8f8 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:22:44 -0500 Subject: [PATCH] Change PEP 746 to use an attribute instead of a method --- peps/pep-0746.rst | 60 +++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/peps/pep-0746.rst b/peps/pep-0746.rst index c2b203ccd70..93c9defb17e 100644 --- a/peps/pep-0746.rst +++ b/peps/pep-0746.rst @@ -45,62 +45,47 @@ Specification ============= This PEP introduces a protocol that can be used by static and runtime type checkers to validate the consistency between ``Annotated`` metadata and a given type. -Objects that implement this protocol have a method named ``__supports_type__`` -that takes a single positional argument and returns ``bool``:: +Objects that implement this protocol have a property, attribute called ``__supports_type__`` +that specifies whether the metadata is valid for a given type. class Int64: - def __supports_type__(self, obj: int) -> bool: - return isinstance(obj, int) + __supports_type__: int -The protocol being introduced would be defined as follows if it were to be defined in code form:: +The attribute may also be marked as a ``ClassVar`` to avoid interaction with dataclasses: + + from dataclasses import dataclass + from typing import ClassVar - from typing import Protocol - - class SupportsType[T](Protocol): - def __supports_type__(self, obj: T, /) -> bool: - ... + @dataclass + class Gt: + value: int + __supports_type__: ClassVar[int] When a static type checker encounters a type expression of the form ``Annotated[T, M1, M2, ...]``, it should enforce that for each metadata element in ``M1, M2, ...``, one of the following is true: * The metadata element evaluates to an object that does not have a ``__supports_type__`` attribute; or -* The metadata element evaluates to an object ``M`` that implements the ``SupportsType`` protocol; - and, with ``T`` instantiated to a value ``v``, a call to ``M.__supports_type__(v)`` type checks without errors; - and that call does not evaluate to ``Literal[False]``. +* The metadata element evaluates to an object ``M`` that has a the ``__supports_type__`` attribute; + and, with ``T`` instantiated to a value ``v``, ``v`` can be assigned to ``M.__supports_type__`` without error. -The body of the ``__supports_type__`` method is not used to check the validity of the metadata -and static type checkers can ignore it. However, tools that use the annotation at -runtime may call the method to check that a particular value is valid. - -For example, to support a generic ``Gt`` metadata, one might write:: +To support a generic ``Gt`` metadata, one might write:: from typing import Protocol class SupportsGt[T](Protocol): def __gt__(self, __other: T) -> bool: ... - + class Gt[T]: + __supports_type__: ClassVar[SupportsGt[T]] + def __init__(self, value: T) -> None: self.value = value - def __supports_type__(self, obj: SupportsGt[T], /) -> bool: - return obj > self.value - x1: Annotated[int, Gt(0)] = 1 # OK x2: Annotated[str, Gt(0)] = 0 # type checker error: str is not assignable to SupportsGt[int] x3: Annotated[int, Gt(1)] = 0 # OK for static type checkers; runtime type checkers may flag this -Implementations may be generic and may use overloads that return ``Literal[True]`` or ``Literal[False]`` -to indicate if the metadata is valid for the given type. - -Implementations may raise a NotImplementedError if they cannot determine if the metadata is valid for the given type. -Tools calling ``__supports_type__`` at runtime should catch this exception and treat it as if ``__supports_type__`` -was not present; they should not take this as an indication that the metadata is invalid for the type. - -Tools that use the metadata at runtime may choose to ignore the implementation of ``__supports_type__``; this PEP does not -specify how the method should be used at runtime, only that it may be available for use. - Backwards Compatibility ======================= @@ -150,10 +135,19 @@ does not generally use marker base classes. In addition, it provides less flexib the current proposal: it would not allow overloads, and it would require metadata objects to add a new base class, which may make their runtime implementation more complex. +Using a method instead of an attribute for ``__supports_type__`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We considered using a method instead of an attribute for the protocol, so that this method can be used +at runtime to check the validity of the metadata and to support overloads or returning boolean literals. +However using a method adds boilerplate to the implementation and the value of the runtime use cases or +more complex scenarios involving overloads and returning boolean literals was not clear. + Acknowledgments =============== -We thank Eric Traut for suggesting the idea of using a protocol. +We thank Eric Traut for suggesting the idea of using a protocol and implementing provisional support in Pyright. +Thank you to Jelle Zijlstra for sponsoring this PEP. Copyright =========