如何让mypy识别经校验后的ForeignKey属性author不为None?
Django模型校验后Mypy无法识别非空属性的解决方案
环境说明
- django 4.2.11
- mypy 1.9.0
- django-stubs 4.2.5
问题描述
编写了关联的Django模型,Book通过ForeignKey关联Author,且author字段设置为null=True:
class Author(models.Model): name = models.CharField() class Book(models.Model): author = models.ForeignKey(Author, blank=False, null=True, ...)
为复用校验逻辑,单独编写了检查Book的author不为None的函数:
def _check_configuration(book: Book) -> None: if not book.author: raise ImproperlyConfigured()
但在调用该函数的业务代码中,Mypy仍报错提示"Author | None" has no attribute "name":
def func1(book: Book) -> int: _check_configuration(book) # 此处触发Mypy报错 author_name = book.author.name
若将检查逻辑直接写在业务函数内,Mypy则能正确识别author非空。
解决方案
要让Mypy识别校验后的author属性必然不为None,可通过以下几种方式实现:
方式一:使用TypeGuard定义类型守卫函数
给校验函数添加TypeGuard返回类型注解,明确告知Mypy:当函数正常返回(不抛出异常)时,传入的Book对象的author属性一定不为None。
修改后的校验函数代码:
from typing import TypeGuard from django.core.exceptions import ImproperlyConfigured def _check_configuration(book: Book) -> TypeGuard[Book]: if book.author is None: raise ImproperlyConfigured() return True
此时业务函数调用该校验函数后,Mypy会自动推断book.author的类型为Author而非Author | None,不再报错。
方式二:在业务函数中添加辅助断言
如果不想修改校验函数,可在业务函数调用校验后添加断言语句,辅助Mypy识别类型:
def func1(book: Book) -> int: _check_configuration(book) # 添加断言明确author非空 assert book.author is not None author_name = book.author.name
这种方式无需改动校验函数,但需要在每个业务调用处添加断言,复用性略差。
方式三:使用typing.cast强制类型转换
作为兜底方案,可通过cast强制指定book.author的类型:
from typing import cast def func1(book: Book) -> int: _check_configuration(book) author = cast(Author, book.author) author_name = author.name
注意:这种方式仅告知Mypy类型,不会在运行时做实际检查,仅建议在其他方式无法使用时采用。
内容的提问来源于stack exchange,提问作者AntonTitov
相关产品推荐
相关产品推荐

