We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Interfaces with Protocols and ABCs step 9 of 26
Make a Protocol Member Read-Only with a Property
The previous exercise showed that a type parameter used only for output can be covariant. A protocol's attribute follows the same rule. If callers can only read it, its type can be covariant. If they can also assign it, its type is invariant, because assigning is an input.
describe(envelope) reads envelope.payload and lists its keys. It accepts
any HasPayload:
class HasPayload(Protocol):
payload: Mapping[str, object]
Three envelope classes should fit, and every test already passes. mypy rejects all three. The first error is:
L45:22 [misc]
Generator has incompatible item type "JsonEnvelope"; expected "HasPayload"
Under each error, a note gives the reason:
Protocol member HasPayload.payload expected settable variable, got read-only attribute.
A plain attribute lets callers assign it
A plain attribute in a protocol lets callers read it and also assign it. That causes two problems here:
- Because callers may assign it, the attribute's type must match exactly.
Code holding a
HasPayloadcould assign anyMapping[str, object], such as{"count": 3}, to aJsonEnvelopewhose field must hold adict[str, str]. - None of the three classes allows assignment. Two are frozen dataclasses,
and
LazyEnvelopehas a property with no setter. So evenFrozenEnvelope, whose field type matches exactly, does not fit.
Make the member a read-only property
describe only reads the payload, so the protocol needs only a getter:
class HasPayload(Protocol):
@property
def payload(self) -> Mapping[str, object]: ...
A property with no setter is read-only, so mypy checks it covariantly. An
implementation can return a more specific type, such as dict[str, str]. A
class can fit a property member with a dataclass field or a plain attribute.
Its member does not need to be a property itself.
What to write
Change only HasPayload. Leave the three envelope classes, describe and
solve as they are. Do not widen the envelope types, and do not use cast.
The driver takes two lists of plain string-to-string dictionaries, mutable
and readonly. The names only say which envelopes each list becomes. The
driver builds the envelopes in three groups:
- A
JsonEnvelopefor each dictionary inmutable, in order. - A
FrozenEnvelopefor each dictionary inreadonly, in order. - A
LazyEnvelopefor each dictionary inreadonly, in order.
describe returns the key count, a colon, and the sorted keys joined by
commas. An empty dictionary gives "0:". For mutable=[] and
readonly=[{"a": "1"}, {"b": "2", "c": "3"}], solve returns
["1:a", "2:b,c", "1:a", "2:b,c"].
The property only stops code assigning envelope.payload through a
HasPayload. It does not freeze the dictionary, so code that holds the
original dict can still change it.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.