Skip to content

PEP 767: Address feedback & open issues#4559

Open
Enegg wants to merge 8 commits intopython:mainfrom
Enegg:pep-767
Open

PEP 767: Address feedback & open issues#4559
Enegg wants to merge 8 commits intopython:mainfrom
Enegg:pep-767

Conversation

@Enegg
Copy link
Contributor

@Enegg Enegg commented Aug 25, 2025

  • Added my real name 🫠
  • Updated Post-History
  • Bikeshedded on some of the wording in this PEP
  • Clarified that use of bare ReadOnly is not allowed, and added that to rejected ideas
  • Specified that a protocol's read-only attributes are not accessible from its type (type[Protocol])
  • Closed open issue: "Extending Initialization"

📚 Documentation preview 📚: https://pep-previews--4559.org.readthedocs.build/

Clarify that bare `ReadOnly` is not allowed, and add that to rejected ideas
Specify that `type[Protocol]` does not inherit the protocol's attributes
Close issue: "Extending Initialization"
Bikeshed on some wording
Use my real name
@Enegg Enegg requested a review from carljm as a code owner August 25, 2025 15:31
Copy link
Member

@hugovk hugovk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we up to with this PR? Can we merge it? Let's do so if there's no further feedback in ~a week.

Enegg and others added 4 commits February 26, 2026 20:40
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
"Terminology" section
allow initializing read-only attributes by subclass
allow `@classmethod` to initialize instance attributes
allow `ReadOnly` + `Final`, though redundant
Comment on lines +31 to +33
This PEP uses "attribute mutation" as a reference to assignment to,
or ``del``\ etion of the attribute.
This is distinct from :term:`mutability <mutable>` of the attribute's value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This terminology seems likely to confuse. The fact that we need to say that "attribute mutation" is different from "mutating the attribute's value" makes it seem like a better term is needed.

I have always used "mutate" to mean changing the value of an object regardless of whether that object is bound to any name, attribute, etc so I already got confused reading this in the diff below:

- The inability to reassign read-only attributes makes them covariant.
+ The inability to mutate read-only attributes makes them covariant.

Is it particularly useful here to use the term "mutating" rather than just saying "reassigning or deleting" which the term is defined here as being an alias for?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. The PEP doesn't actually use the term "mutate" all that often; I don't think it would be onerous to simply say "assign or delete" in those cases. And I think it is confusing to use "mutate" to mean "assign or delete".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a better term is needed

I think it is confusing to use "mutate" to mean "assign or delete".

Hmm, I don't think I've seen anyone refer to "assignment or deletion" collectively with a term other than "mutation".

And to me it makes sense to use it, as the two actions are a form of mutation, just not of the assigned value, but of the object the value is assigned to.

But alright, if it seems I only added to confusion rather than reduce it, I'll change it to say "assign or delete" in those cases.

Comment on lines +31 to +33
This PEP uses "attribute mutation" as a reference to assignment to,
or ``del``\ etion of the attribute.
This is distinct from :term:`mutability <mutable>` of the attribute's value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. The PEP doesn't actually use the term "mutate" all that often; I don't think it would be onerous to simply say "assign or delete" in those cases. And I think it is confusing to use "mutate" to mean "assign or delete".

or ``del``\ etion of the attribute.
This is distinct from :term:`mutability <mutable>` of the attribute's value.

"Read-only" is used to describe attributes which may be read, but not assigned to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"not assigned to (except in limited cases to support initialization)" ?


Use of ``ReadOnly`` in annotations at other sites where it currently has no meaning
(such as local/global variables or function parameters) is considered out of scope
for this PEP.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"...and remains forbidden." ?


def read_games(shelf: HasGames[abc.Sequence[Game]]) -> None:
shelf.games.append(...) # error: "Sequence" has no attribute "append"
# shelf.games.append(...) # error: "Sequence" has no attribute "append"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this line commented?

Comment on lines +259 to +260
Assignment to a read-only attribute can only occur in the class declaring the attribute
and its nominal subclasses, at sites described below.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should clarify somewhere that assignment to / deletion of a readonly attribute on a protocol type is never permitted.

And we should also clarify that a normal writable attribute can implement a protocol with a ReadOnly attribute. (This does not cause unsoundness because the attribute is not writable on the protocol type, where it is covariant.)

Comment on lines 281 to 282
* In ``@classmethod``\ s, on instances of the declaring class created via
a call to the class' or super-class' ``__new__`` method.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems redundant with what's above under "must".

I think we can also combine classmethods into the previous point here.

* At declaration in the body of the class.
* In ``__init_subclass__``, on the class object received as the first parameter (likely, ``cls``).
* At declaration in the class scope.
* In ``__init_subclass__``, on the class object received as the first parameter (usually, ``cls``).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* In ``__init_subclass__``, on the class object received as the first parameter (usually, ``cls``).
* In ``__init_subclass__``, on the class object received as the first parameter (usually ``cls``).

@@ -433,6 +438,8 @@

* Subtypes can :external+typing:term:`narrow` the type of read-only attributes::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Subtypes can :external+typing:term:`narrow` the type of read-only attributes::
* Subclasses can :external+typing:term:`narrow` the type of read-only attributes::

This feature conflicts with :data:`~object.__slots__`. An attribute with
a class-level value cannot be included in slots, effectively making it a class variable.
This is possible only in classes without :data:`~object.__slots__`.
An attribute included in slots cannot have a class-level default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird that this example is under "Class attributes" heading, when class attributes are defined as "annotated as both ReadOnly and ClassVar" -- but here it is not annotated as ClassVar.

I think this should be under instance attributes instead.

Comment on lines 452 to 474
@@ -464,8 +473,9 @@
def pprint(self) -> None:
print(self.foo, self.bar, self.baz)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is phrased as what code authors "should" do, but it does not provide clear specification to type checkers. It seems like type checkers are not required to verify initialization (though they optionally can). This just seems like another variant of "verify initialization". I'm not sure it makes sense as a separate point -- it could be mentioned above in the section about optionally verifying initialization?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants