Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocols with property descriptors require all compliant symbols to use the same implementation. #9202

Open
mostrows2 opened this issue Jul 24, 2020 · 2 comments
Labels
topic-descriptors Properties, class vs. instance attributes

Comments

@mostrows2
Copy link

  • Are you reporting a bug, or opening a feature request?

Bug.

  • Please insert below the code you are checking with mypy,
    or a mock-up repro if the source is private. We would appreciate
    if you try to simplify your case to a minimal repro.

Code snippet attached at end of report.
It appears to be not possible to define a Protocol describing an object that has an attribute named "prop1" with type "int".
Any attempt to do so binds the resulting protocol to the implementation chosen in defining the protocol (i.e. must use the same property/descriptor type).

  • What is the actual behavior/output?
 % mypy /tmp/test.py
/tmp/test.py:72: error: Incompatible types in assignment (expression has type "Foo3", variable has type "Proto1")
/tmp/test.py:72: note: Following member(s) of "Foo3" have conflicts:
/tmp/test.py:72: note:     prop1: expected "int", got "cached_property[int]"
/tmp/test.py:73: error: Incompatible types in assignment (expression has type "Foo4", variable has type "Proto1")
/tmp/test.py:73: note: Following member(s) of "Foo4" have conflicts:
/tmp/test.py:73: note:     prop1: expected "int", got "Property[Foo4, int]"
/tmp/test.py:75: note: Revealed type is 'def (self: test.Foo1) -> builtins.int'
/tmp/test.py:76: note: Revealed type is 'builtins.int'
/tmp/test.py:77: note: Revealed type is 'functools.cached_property[builtins.int*]'
/tmp/test.py:78: note: Revealed type is 'test.Property[test.Foo4*, builtins.int*]'
Found 2 errors in 1 file (checked 1 source file)
  • What is the behavior/output you expect?

No errors should be reported.

  • What are the versions of mypy and Python you are using?

mypy == 0.770 , Python=3.8.4.

  • What are the mypy flags you are using? (For example --strict-optional)

None.

import functools
from typing import Callable, cast, Generic, overload, Protocol, Type, TypeVar, Optional, TYPE_CHECKING, Union

T = TypeVar('T')
V = TypeVar('V')


# Arbitrary descriptor implementation.
class Property(Generic[T, V]):
    def __init__(self, fn: Callable[[T], V]):
        self.fn = fn

    @overload
    def __get__(self, obj: T, owner: Type[T]) -> V: ...

    @overload
    def __get__(self, obj: None, owner: Type[T]) -> Property[T, V]: ...    
     
         
    def __get__(self, obj: Optional[T], owner: Type[T]) -> Union[Property[T, V], V]:
        if obj is None:
            return self
        return self.fn(obj)



# Protocols that define pretty much the same thing.
class Proto1(Protocol):
    @property
    def prop1(self) -> int:
        ...

class Proto2(Protocol):
    prop1: int

class Proto3(Protocol):
    @functools.cached_property
    def prop1(self) -> int:
        ...

class Proto4(Protocol):
    @Property
    def prop1(self) -> int:
        ...        

# Implementation of each protocol above
class Foo1:
    @property
    def prop1(self) -> int:
        return 1

class Foo2:
    def __init__(self, x: int):
        self.prop1 = x
    
class Foo3:
    @functools.cached_property
    def prop1(self) -> int:
        return 3

class Foo4:
    @Property
    def prop1(self) -> int:
        return 4    


# All 4 statements below have RHS values that conform to the behavior
# defined by 'Proto1': objects that have a 'prop1' attribute that is an 'int',
# and that is not settable.
# No errors should be reported.
# It does not appear possible to define a common protocol that all 4 classes
# conform to, even though in code they all have valid common behavior:
# "intVal: int = x.prop1"

x : Proto1 = Foo1()
y : Proto1 = Foo2(2)
z : Proto1 = Foo3()
a : Proto1 = Foo4()

if TYPE_CHECKING:
    reveal_type(Foo1.prop1)
    reveal_type(Foo2.prop1)
    reveal_type(Foo3.prop1)
    reveal_type(Foo4.prop1)    

@mostrows2
Copy link
Author

Would not surprise me if this was effectively the same problem as #7153, but here the issue is with how to define a Protocol.

@JelleZijlstra JelleZijlstra added the topic-descriptors Properties, class vs. instance attributes label Mar 19, 2022
@nejcskofic
Copy link

I was hit today by this as well. It seems there is no way to define read only attribute in protocol, which in concrete implementation can be property, custom descriptor or standard field.

I'm using mypy 1.0.1.

Request for defining read only attribute was also raised in #6002.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-descriptors Properties, class vs. instance attributes
Projects
None yet
Development

No branches or pull requests

3 participants