为何mypy会对assert does_not_raise抛出truthy-function错误?
解决mypy对
assert does_not_raise的类型检查错误 问题背景
接手离职开发者的项目后,遇到一段测试代码触发了mypy的类型检查错误,不想通过# type: ignore跳过检查,希望从根源解决问题。
测试代码
from contextlib import nullcontext as does_not_raise def test_validation_raised_no_error_when_validation_succeeds(): # given given_df = DataFrame(data={"foo": [1, 2], "bar": ["a", "b"]}) given_schema = Schema( [ Column("foo", [InListValidation([1, 2])]), Column("bar", [InListValidation(["a", "b"])]), ] ) # when _validate_schema(given_df, given_schema) # then assert does_not_raise # line 251
mypy错误信息
test/clients/test_my_client.py:251: error: Function "Type[nullcontext[Any]]" could always be true in boolean context [truthy-function] Found 1 error in 1 file (checked 24 source files)
mypy配置
[mypy] python_version = 3.8 warn_return_any = True warn_unused_configs = True ignore_missing_imports = True
问题原因
does_not_raise是nullcontext的别名,本质是一个类(类型对象)。在Python中,所有非空的类型对象在布尔判断里都会被视为True,所以assert does_not_raise等价于assert True——这行代码完全起不到验证_validate_schema是否真的没抛出异常的作用,mypy正是发现了这个逻辑冗余才报错。
原开发者显然混淆了does_not_raise的正确用法:它是用来和pytest.raises配合,在参数化测试中统一断言逻辑的上下文管理器,不是直接用来assert的对象。
正确解决方式
方式1:移除无效断言
如果_validate_schema执行时抛出异常,测试本身就会直接失败,不需要额外的断言语句。直接删掉assert does_not_raise这行代码即可,测试逻辑完全不受影响。
修改后的测试代码:
from contextlib import nullcontext as does_not_raise def test_validation_raised_no_error_when_validation_succeeds(): # given given_df = DataFrame(data={"foo": [1, 2], "bar": ["a", "b"]}) given_schema = Schema( [ Column("foo", [InListValidation([1, 2])]), Column("bar", [InListValidation(["a", "b"])]), ] ) # when _validate_schema(given_df, given_schema) # then - 无需额外断言,无异常即测试通过
方式2:正确使用does_not_raise上下文(适合参数化场景)
如果后续需要扩展成参数化测试(比如同时测试“无异常”和“有特定异常”的场景),可以用上下文管理器包裹执行逻辑,示例:
import pytest from contextlib import nullcontext as does_not_raise @pytest.mark.parametrize( "df, schema, expectation", [ # 无异常的用例 ( DataFrame(data={"foo": [1, 2], "bar": ["a", "b"]}), Schema([Column("foo", [InListValidation([1, 2])]), Column("bar", [InListValidation(["a", "b"])])]), does_not_raise(), ), # 有异常的用例 ( DataFrame(data={"foo": [3], "bar": ["c"]}), Schema([Column("foo", [InListValidation([1, 2])]), Column("bar", [InListValidation(["a", "b"])])]), pytest.raises(ValidationError), ), ] ) def test_validation(df, schema, expectation): with expectation: _validate_schema(df, schema)
这种写法下,does_not_raise()是实例化后的上下文管理器,和pytest.raises()的用法统一,mypy也不会再报错。
内容的提问来源于stack exchange,提问作者oschlueter
相关产品推荐
相关产品推荐

