@@ -98,39 +98,32 @@ Today, there are three major ways of achieving read-only attributes, honored by
9898Protocols
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
1051041. ``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
1101091. 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] ``,
1121113. an instance of the class above,
1131124. an object with a ``@property `` ``def name(self) -> T ``,
1141135. 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
135128Rationale
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