mypy
mypy copied to clipboard
Descriptor protocol is executed for properties returning descriptors
Consider this example:
from __future__ import annotations
class X: ...
class Y:
@property
def some_property(self) -> Descriptor: # return Descriptor instance
return Descriptor()
class Descriptor:
def __get__(self, obj: X, cls: type[X]) -> int: # can work only with X instances
print(f'Descriptor.__get__({self}, {obj}, {cls}) called')
return 0
y = Y()
print(y.some_property)
# arg-type: Argument 1 to "__get__" of "Descriptor" has incompatible type "Y"; expected "X"
# arg-type: Argument 2 to "__get__" of "Descriptor" has incompatible type "Type[Y]"; expected "Type[X]"
# stdout: <__main__.Descriptor object at 0x0000020BF8A5BE20>
# note that Descriptor.__get__ isnt called
I am defining descriptor that can work only with X
instances, and i am returning this descriptor from property of Y
instances.
At runtime Descriptor.__get__
isnt called, but mypy thinks that Y.some_property
is Descriptor
(not a property returning Descriptor
instance), tries to execute descriptor protocol and fails.
At runtime i am getting no errors, descriptor isnt called and this code is safe.
To reproduce:
- just run mypy with this code
Expected Behavior
- no errors
Actual Behavior
- error
Your Environment
- Mypy version used:
mypy 0.971 (compiled: yes)
- Mypy command-line flags: none
- Mypy configuration options from
mypy.ini
(and other config files): none - Python version used: 3.10.6
- Operating system and version: Win10
from __future__ import annotations
class X:
...
class Y:
# some_descriptor: Descriptor
def __init__(self) -> None:
self.some_descriptor = Descriptor()
class Descriptor:
def __get__(self, obj: X, cls: type[X]) -> int:
print(f'Descriptor.__get__({self}, {obj}, {cls}) called')
return 0
y = Y()
print(y.some_descriptor)
This code is fine.
But if i uncomment # some_descriptor: Descriptor
line i am getting errors:
at `self.some_descriptor = Descriptor()` line:
Argument 1 to "__get__" of "Descriptor" has incompatible type "Y"; expected "X"
Argument 2 to "__get__" of "Descriptor" has incompatible type "Type[Y]"; expected "Type[X]"
Incompatible types in assignment (expression has type "Descriptor", variable has type "int")
at `print(y.some_descriptor)` line:
Argument 1 to "__get__" of "Descriptor" has incompatible type "Y"; expected "X"
Argument 2 to "__get__" of "Descriptor" has incompatible type "Type[Y]"; expected "Type[X]"
I think this is related
A similar example with the recent mypy version: https://mypy-play.net/?mypy=latest&python=3.11&gist=069b99fd10df82bef0769c52209ce00e
from typing import assert_type
class Desc:
def __get__(self, obj, owner=None) -> int:
return 1
class C:
a = Desc()
@property
def b(self) -> Desc:
return Desc()
obj = C()
assert_type(obj.a, int)
assert_type(obj.b, Desc)
generates
main.py:15: error: Expression is of type "int", not "Desc" [assert-type]
but works fine at runtime.