I’m working on implementing experimental support for PEP 767 in pyright. I found a number of issues in the PEP that require additional discussion.
- The PEP indicates that a read-only attribute can be overridden by a property or other descriptor object. I don’t think that’s sound because of the covariance of
type. Consider the following code. Regardless of whetherHasName.nameisReadOnly, it is unsound to override it with a descriptor.
from typing import Protocol
class HasName(Protocol):
name: str
def func(h: type[HasName]):
reveal_type(h.name) # Both pyright and mypy reveal str here
print(h.name.upper())
class NamedProp:
@property
def name(self) -> str: ...
# The following call will crash. This should be a type violation,
# and currently mypy and pyright generate an error here. Making
# `name` ReadOnly should not change this.
func(NamedProp)
This affects at least three parts of the spec:
- In the “Rationale” section, the PEP says that “A subclass of
Membercan redefine.idas a writable attribute or a descriptor”. I think you need to delete “or a descriptor”. - The “Subtyping” section says “Read-only attributes can be redeclared as writable attributes, descriptors or class variables”. For the same reason here, “descriptors” cannot be allowed.
- In the “Subtyping” section, one of the examples (
NamedProp) should be deleted because this is unsound. Similarly,NamedDescriptorshould be deleted.
-
The PEP is not clear on whether a “bare”
ReadOnlyannotation is allowed or should be considered an error. One could argue that this should be an error condition sinceReadOnlyis a type qualifier, so it should have a corresponding type to qualify. However, one could also make the argument that an implicit type ofAnycould be assumed if a type annotation is omitted. One could also make the argument that if a bareReadOnlyis coupled with an assignment, inference should be used (as it is with theFinalqualifier); however, inference rules would get tricky to define here because (unlikeFinal), it’s inadvisable to infer literal values because this could cause problems for overrides within subclasses. In any case, additional clarity is needed here. -
The “Initialization” section includes a comment “cannot assign to a read-only attribute of a base class” even though the code is located within an
__init__method. This seems inconsistent with the fact that a subclass can override a read-only attribute. If it’s able to override, why can’t it reassign a new value in the__init__method (or__new__or__init_subclass__) without generating an error? -
There is an “Open Issues” section that still needs to be resolved. My take on the open issue is that third-party initialization hooks should be out of scope. The initialization rules in this PEP are already pretty onerous for type checkers to implement. Extending these rules to arbitrary third-party initialization mechanisms is not feasible IMO.