Suk*_*mbu 6 python type-hinting mypy
#!/usr/bin/env python3
from abc import ABCMeta, abstractmethod
class Base(metaclass = ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
return (
hasattr(subclass, 'x')
)
@property
@abstractmethod
def x(self) -> float:
raise NotImplementedError
class Concrete:
x: float = 1.0
class Application:
def __init__(self, obj: Base) -> None:
print(obj.x)
ob = Concrete()
app = Application(ob)
print(issubclass(Concrete, Base))
print(isinstance(Concrete, Base))
print(type(ob))
print(Concrete.__mro__)
Run Code Online (Sandbox Code Playgroud)
python test_typing.py返回:
1.0
True
False
<class '__main__.Concrete'>
(<class '__main__.Concrete'>, <class 'object'>)
Run Code Online (Sandbox Code Playgroud)
并mypy test_typing.py返回:
test_typing.py:30: error: Argument 1 to "Application" has incompatible type "Concrete"; expected "Base"
Found 1 error in 1 file (checked 1 source
Run Code Online (Sandbox Code Playgroud)
但如果我将行更改class Concrete:为class Concrete(Base):,我会得到
python test_typing.py以下结果:
1.0
True
False
<class '__main__.Concrete'>
(<class '__main__.Concrete'>, <class '__main__.Base'>, <class 'object'>)
Run Code Online (Sandbox Code Playgroud)
为此mypy test_typing.py:
Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)
如果我将以下代码添加到我的代码中:
reveal_type(Concrete)
reveal_type(Base)
Run Code Online (Sandbox Code Playgroud)
我在这两种情况下都得到相同的结果mypy test_typing.py:
test_typing.py:37: note: Revealed type is "def () -> vmc.test_typing.Concrete"
test_typing.py:38: note: Revealed type is "def () -> vmc.test_typing.Base"
Run Code Online (Sandbox Code Playgroud)
似乎很明显,MyPi 在虚拟基类方面存在一些问题,但非虚拟继承似乎按预期工作。
在这些情况下 MyPy 的类型估计如何工作?有解决方法吗?
使用Protocol模式:
1.0
True
False
<class '__main__.Concrete'>
(<class '__main__.Concrete'>, <class 'object'>)
Run Code Online (Sandbox Code Playgroud)
mypy:Success: no issues found in 1 source fileisinstance(Concrete, Base):Trueissubclass(Concrete, Base):TypeError: Protocols with non-method members don't support issubclass()__init__方法签名:__init__(self, x: float) -> Nonevs. __init__(self) -> None(为什么在这里返回inspect.signature()字符串(self, *args, **kwargs)and (self, /, *args, **kwargs)?用class Base:而不是class Base(Protocol):i get (self, x: float) -> Noneand (self, /, *args, **kwargs))@abstractmethod忽略和之间的区别@classmethod(将任何方法视为抽象)这次只是第一个代码的更复杂的示例:
test_typing.py:30: error: Argument 1 to "Application" has incompatible type "Concrete"; expected "Base"
Found 1 error in 1 file (checked 1 source
Run Code Online (Sandbox Code Playgroud)
issubclass(Concrete, Base):Trueisinstance(Concrete, Base):False__init__.test_typing.py:42: error: Argument 1 to "Application" has incompatible type "Concrete"; expected "Base"
Found 1 error in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)
在某些情况下,以下代码可能是一个可能的解决方案。
1.0
True
False
<class '__main__.Concrete'>
(<class '__main__.Concrete'>, <class '__main__.Base'>, <class 'object'>)
Run Code Online (Sandbox Code Playgroud)
__init__由于使用而自动生成的方法@dataclass。这里:(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, w: float = 1.0) -> Noneisinstance():TrueSuccess: no issues found in 1 source file@dataclass在实现您的界面时使用它。__init__不仅采用类属性的方法。提示:如果不需要强制__init__方法并且只想处理属性,那么只需省略@dataclass。
更新了第四个代码以提供更多安全性,但没有隐式__init__方法:
Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)
路Protocol不稳。但这种Metaclass方式是不可检查的,因为它不能与 MyPy 一起使用(因为它不是静态的)。
是否有任何替代解决方案来实现某种类型的接口(不带class Concrete(Base))并使其类型安全(可检查)?
经过一些测试和更多研究后,我确信实际问题是Protocol默默地覆盖定义的__init__方法的行为。
似乎合乎逻辑,因为协议并不是要启动的。但有时需要定义一个__init__方法,因为在我看来,__init__方法也是类及其对象接口的一部分。
我发现了一个关于这个问题的现有问题,这似乎证实了我的观点:https ://github.com/python/cpython/issues/88970
幸运的是它已经修复: https://github.com/python/cpython/commit/5f2abae61ec69264b835dcabe2cdabe57b9a990e
但不幸的是,此修复仅适用于 Python 3.11 及更高版本。
目前可用的 Python 3.10.5。
警告:就像问题中提到的那样,某些静态类型检查器在这种情况下可能表现不同。MyPy只是忽略丢失的__init__方法(测试过,确认),但Pyright似乎检测并报告丢失的__init__方法(未经我测试)。
| 归档时间: |
|
| 查看次数: |
1592 次 |
| 最近记录: |