Skip to content

Commit 15b4ed6

Browse files
committed
Rewrite the Protocols section
1 parent df4ddc3 commit 15b4ed6

File tree

1 file changed

+18
-23
lines changed

1 file changed

+18
-23
lines changed

peps/pep-0767.rst

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,39 +98,32 @@ Today, there are three major ways of achieving read-only attributes, honored by
9898
Protocols
9999
---------
100100

101-
Paraphrasing `this post <https://github.com/python/typing/discussions/1525>`_,
102-
there is no way of defining an attribute ``name: T`` on a :class:`~typing.Protocol`,
103-
such that the only requirements to satisfy are:
101+
A read-only structural attribute ``name: T`` on a :class:`~typing.Protocol` in principle
102+
defines the following two requirements:
104103

105104
1. ``hasattr(obj, "name")``
106-
2. ``isinstance(obj.name, T)`` [#invalid_typevar]_
105+
2. ``isinstance(obj.name, T)``
107106

108-
The above are satisfiable at runtime by all of the following:
107+
Those requirements are satisfiable at runtime by all of the following:
109108

110109
1. an object with an attribute ``name: T``,
111-
2. a class with a class variable ``name: ClassVar[T]``, [#invalid_typevar]_
110+
2. a class with a class variable ``name: ClassVar[T]``,
112111
3. an instance of the class above,
113112
4. an object with a ``@property`` ``def name(self) -> T``,
114113
5. an object with a custom descriptor, such as :func:`functools.cached_property`.
115114

116-
Note that the attribute being marked ``Final`` or the property defining a setter
117-
do not impact this.
118-
119-
The most common practice is to define such a protocol with a ``@property``::
115+
The current `typing spec <https://typing.readthedocs.io/en/latest/spec/protocol.html#protocol-members>`_
116+
defines that read-only protocol variables can be created using (abstract) properties::
120117

121118
class HasName(Protocol):
122119
@property
123120
def name(self) -> T: ...
124121

125-
Type checkers special-case this definition, such that objects with plain attributes
126-
are assignable to the type. However, instances with class variables and descriptors
127-
other than ``property`` are rejected.
128-
129-
Covering the extra possibilities induces a great amount of boilerplate, involving
130-
creation of an abstract descriptor protocol, possibly also accounting for
131-
class and instance level overloads.
132-
Worse yet, all of that is multiplied for each additional read-only attribute.
133-
122+
- The syntax is somewhat verbose.
123+
- It is not obvious that the quality conveyed here is the read-only character of a property.
124+
- Not composable with ``ClassVar`` or ``Annotated``.
125+
- Not all type checkers agree [#property_in_protocol]_ that all of the above five
126+
objects are assignable to this structural type.
134127

135128
Rationale
136129
=========
@@ -162,7 +155,7 @@ A class with a read-only instance attribute can now be defined as::
162155

163156
* A subclass of ``Member`` can redefine ``.id`` as a writable attribute or a
164157
:term:`descriptor`. It can also :external+typing:term:`narrow` the type.
165-
* The ``HasName`` protocol can be implemented by any mechanism allowing for ``.name`` access.
158+
* The ``HasName`` protocol succinctly expresses operations available on its attribute.
166159
* The ``greet`` function can now accept a wide variety of compatible objects,
167160
while being explicit about no modifications being done to the input.
168161

@@ -538,9 +531,11 @@ Footnotes
538531
This PEP focuses solely on the type-checking behavior. Nevertheless, it should
539532
be desirable the name is read-only at runtime.
540533
541-
.. [#invalid_typevar]
542-
The implied type variable is not valid in this context; it has been used for
543-
the ease of demonstration. See `ClassVar <https://typing.readthedocs.io/en/latest/spec/class-compat.html#classvar>`_.
534+
.. [#property_in_protocol]
535+
Pyright disallows class variable and non-property descriptor overrides.
536+
`[Pyright] <https://pyright-play.net/?pyrightVersion=1.1.389&pythonVersion=3.13&strict=true&code=GYJw9gtgBAhgRgYygSwgBzCALrOBnLEGBLCAUywAswATAKFEimAFcA7EsMAGzxXUw4ExSmRoB9NODRlsATwbhoWOWmRsA5vwzYoAYW4w8eAGowQAGigAFcFjAIeV4Opjc6HhIeNQAEkYAxLgAKWzB7R24ASgAuOigEqAABKTAZeXjEpPgCIhJyKlpMhJoyYGYQvDJuYCioAFoAPhQ2LBioADoujzoAYlhjZA02eGRuZBUeryM%2BILAAQSxCZDgWLDI4xIqwdvUsT29ZrjD0lU2s1NOFLdLy4Erq2obmvfaQChYQNigABgOZqBzAwzMwgc4Je47fSHUEAbT2AF0oABeX7-HxzAAiZDwCBAyDQ9jBxWSwgQogkl1kkxuZW2wSqNTqTRabSg7ywn2%2Bfzo0wxx2k1LkejAADdzMgYK1wckqRlaXcHkznlA4FxuG8Pl9AW4quijmAAJJscXjGgyyHtXL6qAAOTAcxlcHMVsIPTAcAAVu1-Hg5nQPZ6UYCuItlqt1sE6lB%2BmAANYBr3BuYnIVRxKxhOB5NcYHGUFbGNQeOJoOooEw8zphKZ0s5sDY3H4wmYdO17PlgVpIUi8X4qVYNvFrNJztGk1uZA0as1qCyEB11H2uYzwtF%2BeLu1gNhkNdr-objwHgAeaHGCAm2ncNrmYfxEbIhvQ3GCvrmsRJltZN67VyfZ9fQIuA-LYUkFeVEluelGSeFlXnZLVuR-MA81Mcx-xfN9gItLh2lQuFEWDHk%2BQNRs8QJIkMMAv1sJJJIyQpSRwJpSC6UhBlHmZF5pQQzltWIw4QzAVN5F7CUByorCwBAi5mOuVjFTADjlRZNUeE1PjvgCXUyGQ41TSnSSgOknCoWtOgkhcEZ3BIrc5iMmiTJJZ0wSga0gA>`_
537+
`[mypy] <https://mypy-play.net/?mypy=1.13.0&python=3.12&flags=strict&gist=12d556bb6ef4a9a49ff4ed4776604750>`_
538+
`[Pyre] <https://pyre-check.org/play/?input=%23%20pyre-strict%0Afrom%20abc%20import%20abstractmethod%0Afrom%20functools%20import%20cached_property%0Afrom%20typing%20import%20ClassVar%2C%20Protocol%2C%20final%0A%0A%0Aclass%20HasFoo(Protocol)%3A%0A%20%20%20%20%40property%0A%20%20%20%20%40abstractmethod%0A%20%20%20%20def%20foo(self)%20-%3E%20int%3A%20...%0A%0A%0A%23%20assignability%0A%0A%0Aclass%20FooAttribute%3A%0A%20%20%20%20foo%3A%20int%0A%0Aclass%20FooProperty%3A%0A%20%20%20%20%40property%0A%20%20%20%20def%20foo(self)%20-%3E%20int%3A%20return%200%0A%0Aclass%20FooClassVar%3A%0A%20%20%20%20foo%3A%20ClassVar%5Bint%5D%20%3D%200%0A%0Aclass%20FooDescriptor%3A%0A%20%20%20%20%40cached_property%0A%20%20%20%20def%20foo(self)%20-%3E%20int%3A%20return%200%0A%0Aclass%20FooPropertyCovariant%3A%0A%20%20%20%20%40property%0A%20%20%20%20def%20foo(self)%20-%3E%20bool%3A%20return%20False%0A%0Aclass%20FooInvalid%3A%0A%20%20%20%20foo%3A%20str%0A%0Aclass%20NoFoo%3A%0A%20%20%20%20bar%3A%20str%0A%0A%0Aobj%3A%20HasFoo%0Aobj%20%3D%20FooAttribute()%20%20%23%20ok%0Aobj%20%3D%20FooProperty()%20%20%20%23%20ok%0Aobj%20%3D%20FooClassVar%20%20%20%20%20%23%20ok%0Aobj%20%3D%20FooClassVar()%20%20%20%23%20ok%0Aobj%20%3D%20FooDescriptor()%20%23%20ok%0Aobj%20%3D%20FooPropertyCovariant()%20%23%20ok%0Aobj%20%3D%20FooInvalid()%20%20%20%20%23%20err%0Aobj%20%3D%20NoFoo()%20%20%20%20%20%20%20%20%20%23%20err%0Aobj%20%3D%20None%20%20%20%20%20%20%20%20%20%20%20%20%23%20err%0A%0A%0A%23%20explicit%20impl%0A%0A%0Aclass%20FooAttributeImpl(HasFoo)%3A%0A%20%20%20%20foo%3A%20int%0A%0Aclass%20FooPropertyImpl(HasFoo)%3A%0A%20%20%20%20%40property%0A%20%20%20%20def%20foo(self)%20-%3E%20int%3A%20return%200%0A%0Aclass%20FooClassVarImpl(HasFoo)%3A%0A%20%20%20%20foo%3A%20ClassVar%5Bint%5D%20%3D%200%0A%0Aclass%20FooDescriptorImpl(HasFoo)%3A%0A%20%20%20%20%40cached_property%0A%20%20%20%20def%20foo(self)%20-%3E%20int%3A%20return%200%0A%0Aclass%20FooPropertyCovariantImpl(HasFoo)%3A%0A%20%20%20%20%40property%0A%20%20%20%20def%20foo(self)%20-%3E%20bool%3A%20return%20False%0A%0Aclass%20FooInvalidImpl(HasFoo)%3A%0A%20%20%20%20foo%3A%20str%0A%0A%40final%0Aclass%20NoFooImpl(HasFoo)%3A%0A%20%20%20%20bar%3A%20str%0A>`_
544539
545540
.. [#final_mutability]
546541
As noted above the second-to-last code example of https://typing.readthedocs.io/en/latest/spec/qualifiers.html#semantics-and-examples

0 commit comments

Comments
 (0)