4
from typing_extensions import Protocol


class IFoo(Protocol):
    value: int


class Foo(IFoo):
    @property
    def value(self) -> int:
        return 2

    _value: int

    @value.setter
    def value(self, value: int):
        self._value = value

Pylance in strict mode(basic mode doesn't) is giving an error at the getter and the setter saying that:

"value" overrides symbol of the same name in class "IFoo" "property" is incompatible with "int".

I could make this work by changing the Protocol to:

class IFoo(Protocol):
    @property
    def value(self) -> int:
        raise NotImplemented

But this now makes this invalid:

class Foo(IFoo):
    value: int

This doesn't makes sense, the Foo would still have the property value that is an int, why being a getter should makes it different (in typescript this doesn't make a difference)? How can I fix this?

metatoaster
  • 17,419
  • 5
  • 55
  • 66
vacih86456
  • 237
  • 1
  • 7

1 Answers1

0

Reading the Defining a protocol section of the relevant pep (PEP 544), the example implementation (in their case, class Resource) does not directly inherit from the protocol - their class SupportsClose functions as a reference type for type hinting validators.

Your example is also reminiscent of the long established zope.interface package, which this PEP also referenced. Note that the example usage the PEP have cited the following example (irrelevant lines trimmed):

from zope.interface import Interface, implementer

class IEmployee(Interface):
   ...

@implementer(IEmployee)
class Employee:
   ...

The Employee class does not directly subclass from IEmployee (a common mistake for newbie Zope/Plone developers back in the days), it's simply decorated with the zope.interface.implementer(IEmployee) class decorator to denote that the class Employee implements from the interface IEmployee.

Likewise, reading further down under the section Protocol members, we have an example of the template and concrete classes (again, example trimmed):

from typing import Protocol

class Template(Protocol):
    name: str        # This is a protocol member
    value: int = 0   # This one too (with default)

class Concrete:
    def __init__(self, name: str, value: int) -> None:
        self.name = name
        self.value = value

var: Template = Concrete('value', 42)  # OK

Again, note that the Concrete implementation does not inherit from the Template, yet, the variable var is denoted to have an expected type of Template. Note that the Concrete instance can be assigned to it as it matches the expected protocol as defined for Template.

So all that being said, given your example, you may wish to define class Foo: as is rather than having it inherit from IFoo as you had originally, and fill in the type information such that things that expect IFoo be type hinted as appropriately in the relevant context (e.g. some_foo: IFoo = Foo(...) or def some_func(foo: IFoo):).

As an addendum, you may wish to define Foo as such:

class Foo:
    _value: int

    @property
    def value(self) -> int:
        return 2

    @value.setter
    def value(self, value: int):
        self._value = value

Having the _value definition in between the property and its setter seems to confuse mypy due to this issue.

metatoaster
  • 17,419
  • 5
  • 55
  • 66