You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -105,6 +111,9 @@ The above are satisfiable at runtime by all of the following:
105
111
4. an object with a ``@property`` ``def name(self) -> T``,
106
112
5. an object with a custom descriptor, such as :func:`functools.cached_property`.
107
113
114
+
Note that the attribute being marked ``Final`` or the property defining a setter
115
+
do not impact this.
116
+
108
117
The most common practice is to define such a protocol with a ``@property``::
109
118
110
119
class HasName[T](Protocol):
@@ -117,21 +126,22 @@ other than ``property`` are rejected.
117
126
118
127
Covering the extra possibilities induces a great amount of boilerplate, involving
119
128
creation of an abstract descriptor protocol, possibly also accounting for
120
-
class vs instance level overloads.
129
+
class and instance level overloads.
121
130
Worse yet, all of that is multiplied for each additional read-only attribute.
122
131
123
132
124
133
Rationale
125
134
=========
126
135
127
-
This problem can be resolved by an attribute-level type qualifier. ``ReadOnly``
136
+
These problems can be resolved by an attribute-level type qualifier. ``ReadOnly``
128
137
has been chosen for this role, as its name conveys the intent well, and the newly
129
138
proposed changes complement its semantics defined in :pep:`705`.
130
139
131
140
A class with a read-only instance attribute can be now defined as such::
132
141
133
142
from typing import ReadOnly
134
143
144
+
135
145
class Member:
136
146
id: ReadOnly[int]
137
147
@@ -142,19 +152,19 @@ A class with a read-only instance attribute can be now defined as such::
142
152
143
153
from typing import Protocol, ReadOnly
144
154
155
+
145
156
class HasName(Protocol):
146
157
name: ReadOnly[str]
147
158
159
+
148
160
def greet(obj: HasName, /) -> str:
149
161
return f"Hello, {obj.name}!"
150
162
151
-
A subclass of ``Member`` can redefine ``id`` as a ``property`` or writable
152
-
attribute, while staying compatible with the base class.
153
-
154
-
The ``HasName`` protocol can be implemented by any mechanism allowing for ``.name`` access.
155
-
156
-
The ``greet`` function can now accept a wide variety of compatible objects,
157
-
while being explicit about no modifications being done to the input.
163
+
* A subclass of ``Member`` can redefine ``id`` as a ``property`` or writable
164
+
attribute, while staying compatible with the base class.
165
+
* The ``HasName`` protocol can be implemented by any mechanism allowing for ``.name`` access.
166
+
* The ``greet`` function can now accept a wide variety of compatible objects,
167
+
while being explicit about no modifications being done to the input.
158
168
159
169
160
170
Specification
@@ -163,12 +173,46 @@ Specification
163
173
The :external+py3.13:data:`typing.ReadOnly` type qualifier becomes a valid annotation
164
174
for attributes of classes and protocols.
165
175
166
-
Classes
167
-
-------
176
+
It remains invalid in annotations of global and local variables, as in those contexts
177
+
it would have the same meaning as using ``Final``.
178
+
179
+
Syntax
180
+
------
181
+
182
+
``ReadOnly`` can be used at class-level or within ``__init__`` to declare
183
+
an attribute read-only:
184
+
185
+
.. code-block:: python
186
+
187
+
classBase:
188
+
id: ReadOnly[int]
189
+
190
+
def__init__(self, id: int, rate: float) -> None:
191
+
self.id =id
192
+
self.rate: ReadOnly = rate
193
+
194
+
The explicit type in ``ReadOnly[<type>]`` can be omitted if an initializing value
195
+
is assigned to the attribute. A type checker should apply its usual type inference
196
+
rules to determine the type of ``rate``.
168
197
169
-
In a class attribute declaration, ``ReadOnly`` indicates that assignment to the
170
-
attribute can only occur as a part of the declaration, or within a set of
171
-
initializing methods in the same class::
198
+
In contexts where an attribute is already implied to be read-only, like in the
199
+
frozen :ref:`classes`, it should be valid to explicitly declare it ``ReadOnly``:
200
+
201
+
.. code-block:: python
202
+
203
+
@dataclass(frozen=True)
204
+
classPoint:
205
+
x: ReadOnly[int]
206
+
y: ReadOnly[int]
207
+
208
+
Initialization
209
+
--------------
210
+
211
+
Assignment to a ``ReadOnly`` attribute can only occur as a part of the declaration,
212
+
or within ``__init__`` of the same class. There is no restriction to how many
213
+
times the attribute can be assigned to in those contexts. Example:
214
+
215
+
.. code-block:: python
172
216
173
217
from collections import abc
174
218
from typing import ReadOnly
@@ -187,37 +231,41 @@ initializing methods in the same class::
187
231
self.songs =list(songs)
188
232
189
233
defclear(self) -> None:
190
-
self.songs = [] # Type check error: "songs" is read only
234
+
# Type check error: assignment to read-only "songs" outside initialization
235
+
self.songs = []
191
236
192
237
193
-
band = Band("Boa", ["Duvet"])
238
+
band = Band(name="Boa", songs=["Duvet"])
194
239
band.name ="Emma"# Ok: "name" is not read-only
195
240
band.songs = [] # Type check error: "songs" is read-only
196
241
band.songs.append("Twilight") # Ok: list is mutable
197
242
198
-
The set of initializing methods consists of ``__new__`` and ``__init__``.
199
-
However, type checkers may permit assignment in additional `special methods <https://docs.python.org/3/glossary.html#term-special-method>`_
200
-
to facilitate 3rd party mechanisms such as dataclasses' `__post_init__ <https://docs.python.org/3/library/dataclasses.html#dataclasses.__post_init__>`_.
201
-
It should be non-ambiguous that those methods are not called outside initialization.
243
+
Classes which do not define `__slots__ <https://docs.python.org/3/reference/datamodel.html#object.__slots__>`_
244
+
may give the attribute a default value, overridable at instance level:
202
245
203
-
A read-only attribute with an initializer remains assignable to during initialization::
246
+
.. code-block:: python
204
247
205
248
classFoo:
206
249
number: ReadOnly[int] =0
207
250
208
251
def__init__(self, number: int|None=None) -> None:
209
252
if number isnotNone:
210
-
self.number = number
253
+
self.number = number
211
254
212
-
Note that this cannot be used in a class defining ``__slots__``, as slots prohibit
213
-
the existence of class-level and instance-level attributes of same name.
214
-
215
-
Protocols
216
-
---------
255
+
Inheritance
256
+
-----------
217
257
218
258
In a protocol attribute declaration, ``name: ReadOnly[T]`` indicates that a structural
219
259
subtype must support ``.name`` access, and the returned value is compatible with ``T``.
220
260
261
+
Changes to ``Final``
262
+
--------------------
263
+
264
+
.. TODO
265
+
once changes are done, this probably won't be true
266
+
``ReadOnly`` cannot be combined with ``Final``, as the two qualifiers differ in
267
+
initialization rules, leading to ambiguity and/or significance of ordering.
268
+
221
269
Interaction with other special types
222
270
------------------------------------
223
271
@@ -238,11 +286,8 @@ Interaction with other special types
238
286
This is consistent with the interaction of ``ReadOnly`` and :class:`typing.TypedDict`
239
287
defined in :pep:`705`.
240
288
241
-
The combination of ``ReadOnly`` and ``ClassVar`` imposes the attribute must be
242
-
initialized in the class scope, as there are no other valid initialization scopes.
243
-
244
-
``ReadOnly`` cannot be combined with ``Final``, as the two qualifiers define incompatible
245
-
initialization rules, leading to ambiguity and/or significance of ordering.
289
+
``ClassVar`` excludes read-only attributes from being assignable to within
290
+
initialization methods.
246
291
247
292
248
293
Backwards Compatibility
@@ -252,10 +297,10 @@ This PEP introduces new contexts where ``ReadOnly`` is valid. Programs inspectin
252
297
those places will have to change to support it. This is expected to mainly affect type checkers.
253
298
254
299
However, caution is advised while using the backported ``typing_extensions.ReadOnly``
255
-
in older versions of Python, especially in conjunction with other type qualifiers;
256
-
not all nesting orderings might be treated equal. In particular, the ``@dataclass``
257
-
decorator which looks for ``ClassVar`` will incorrectly treat
258
-
``ReadOnly[ClassVar[...]]`` as an instance attribute.
300
+
in older versions of Python. Mechanisms inspecting annotations may behave incorrectly
301
+
when encountering ``ReadOnly``; in particular, the ``@dataclass`` decorator
302
+
which `looks for <https://docs.python.org/3/library/dataclasses.html#class-variables>`_
303
+
``ClassVar`` will incorrectly treat ``ReadOnly[ClassVar[...]]`` as an instance attribute.
259
304
260
305
261
306
Security Implications
@@ -270,6 +315,19 @@ How to Teach This
270
315
[How to teach users, new and experienced, how to apply the PEP to their work.]
271
316
272
317
318
+
Rejected ideas
319
+
==============
320
+
321
+
Assignment in ``__post_init__``
322
+
-------------------------------
323
+
324
+
An earlier version of this PEP specified that assignment to read-only attributes
325
+
may also be permitted within methods augmenting initialization, such as
0 commit comments