假设我有一个简单的协议
A
和一个未能实现该协议的类
B
:
from typing import Protocol
class A(Protocol):
def foo(self) -> str: ...
class B:
pass
当下面的代码进行类型检查时,Mypy 将正确地抱怨
x: A = B()
mypy .
error: Incompatible types in assignment (expression has type "B", variable has type "A")
如果我有它,那么
B
继承from
django.db.models.Model
如下
class A(Protocol):
def foo(self) -> str: ...
class B(models.Model):
pass
x: A = B()
mypy 在检查行
x: A = B()
时不再抛出错误。我本以为它会抛出错误,因为
B
没有(看似)实现
A
协议。我认为这一定与
models.Model
的一些继承属性有关,这使得mypy认为
foo
方法在那里。
有谁知道这里发生了什么,并就如何让mypy检查是否或有建议Django 模型没有实现协议吗?请注意,我希望模型不需要显式地对协议进行子类化。
观察到的行为是由于
django.db.models.Model
与 mypy 的交互方式造成的,尤其是在处理与运行时类型信息相关的类型提示时。以下是发生情况的简要细分:
-
Django 模型的动态性质: Django 模型旨在具有高度动态性。在运行时,根据在模型定义中定义的字段以及从数据库架构中推断的其他信息,将向它们添加属性(例如,数据库列)。
-
mypy 和运行时信息: mypy 主要是一个静态类型检查器。虽然它在处理某些类型的运行时信息方面做得越来越好,但它不能总是完全模拟 Django 模型在运行时发生的动态属性添加。
-
推断和
Any
: 当 mypy 遇到它没有足够类型信息的情况(例如,动态添加的 Django 模型属性)时,它通常会默认使用Any
类型。Any
类型实质上是一个万能类型,这意味着 mypy 不会对其进行任何类型检查。
在的情况下,即使
B
没有显式实现
foo
,mypy 也会看到
B
继承自
models.Model
。由于 Django 模型的动态性质,mypy 不能确定
B
在运行时是否
不会
具有
foo
方法(它可能来自数据库字段或其他机制)。因此,为了安全起见,mypy 不再引发错误,因为它假设可能存在在静态分析期间无法检测到的
foo
实现。
使 mypy 更严格
可以通过禁用
ignore_missing_imports
并为 Django 使用插件来提高 mypy 的严格性。这将有助于 mypy 更准确地理解 Django 模型,并可能在缺少的协议方法上提供更准确的类型检查。
解决方法
可以考虑以下解决方法来解决此问题:
-
显式协议实现: 最明确的解决方案是修改的模型以显式实现协议:
python class B(models.Model): def foo(self) -> str: return "This is foo from B"
-
自定义类型提示: 如果由于某种原因无法修改模型,可以创建更精确的自定义类型提示来描述的模型,从而有可能在静态分析期间捕获不兼容性。
-
运行时检查: 作为最后的手段,可以使用运行时检查来验证的模型是否符合协议: ```python from typing import TypeGuard
@TypeGuard def implements_a(obj: object) -> TypeGuard[A]: return hasattr(obj, 'foo') and callable(obj.foo)
def my_function(a_instance: A): ...
b_instance = B() if implements_a(b_instance): my_function(b_instance) # Type safe within the if block ```
请记住,Django 模型的动态性本质上使其在静态类型检查方面具有挑战性。使用上述方法可以找到一个平衡点,该平衡点在保持代码库类型安全的期望水平与 Django 提供的灵活性和动态性之间取得平衡。
标签:python,django,python-typing From: 77076912