How to declare a Protocol with a field which supports both a simple type and property?
Question:
(Related, but not duplicated: How to annotate attribute that can be implemented as property?)
I want to create a Protocol
, in which a field can be implemented by both a simple type and property. For example:
class P(Protocol):
v: int
@dataclass
class Foo(P):
v: int
class Bar(P):
@property
def v(self) -> int: # ERROR
return
But the code above doesn’t type check. How should I fix it?
Note: I want to solve this issue without rewriting Foo
and Bar
, because Foo
and Bar
are not what I implemented.
According to this issue the below code is not a solution because read-only property
and a simple member have subtly different semantics.
class P(Protocol):
@property
def v(self) -> int: # declare as property
...
Pyright denies this Protocol
due to the difference.
Answers:
In general, declare the Protocol
using a read-only property
, not a read/write field:
class P(Protocol):
@property
def v(self) -> int:
pass
This is needed because a read-only protocol attribute is satisfied by both a read-only property
and a read/write field. In contrast, a read/write protocol attribute is satisfied only by a read/write field, not a read-only property
.
As PyRight insists that fields and properties are different kinds of attributes, the attribute must be declared with both variants – once as a field and once as an attribute. For simple protocols, this can be done by declaring a separate field and property variant of the property:
# field only
class Pf(Protocol):
v: int
# property only
class Pp(Protocol):
@property
def v(self) -> int:
return 1
# Either field or property
P = Union[Pf, Pp]
This is valid for both MyPy and PyRight.
If you are not strict about the type annotated for the property’s getter, you could define P
as:
from typing import Protocol, Union, ClassVar
class P(Protocol):
v: Union[int, ClassVar]
(Related, but not duplicated: How to annotate attribute that can be implemented as property?)
I want to create a Protocol
, in which a field can be implemented by both a simple type and property. For example:
class P(Protocol):
v: int
@dataclass
class Foo(P):
v: int
class Bar(P):
@property
def v(self) -> int: # ERROR
return
But the code above doesn’t type check. How should I fix it?
Note: I want to solve this issue without rewriting Foo
and Bar
, because Foo
and Bar
are not what I implemented.
According to this issue the below code is not a solution because read-only property
and a simple member have subtly different semantics.
class P(Protocol):
@property
def v(self) -> int: # declare as property
...
Pyright denies this Protocol
due to the difference.
In general, declare the Protocol
using a read-only property
, not a read/write field:
class P(Protocol):
@property
def v(self) -> int:
pass
This is needed because a read-only protocol attribute is satisfied by both a read-only property
and a read/write field. In contrast, a read/write protocol attribute is satisfied only by a read/write field, not a read-only property
.
As PyRight insists that fields and properties are different kinds of attributes, the attribute must be declared with both variants – once as a field and once as an attribute. For simple protocols, this can be done by declaring a separate field and property variant of the property:
# field only
class Pf(Protocol):
v: int
# property only
class Pp(Protocol):
@property
def v(self) -> int:
return 1
# Either field or property
P = Union[Pf, Pp]
This is valid for both MyPy and PyRight.
If you are not strict about the type annotated for the property’s getter, you could define P
as:
from typing import Protocol, Union, ClassVar
class P(Protocol):
v: Union[int, ClassVar]