Python中能否将对象赋值给callable/函数?Mypy报错问题求解
我们知道,在Python中函数本身属于类。在某种程度上,我们可以将其视为支持调用、调用后返回值的数据类型,即callable(可调用对象)。同时Python中的类也属于callable,调用类的过程本质上是创建类的实例对象的过程。
在当前开发任务中,定义了如下包含两个方法的类:
class SomeClass(): def some_method_1(): some_code def some_method_2(): some_code self.some_method_1() some_code
上述代码中,some_method_2方法内部调用了some_method_1。现在需要对some_method_2开展单元测试,因此将实例的some_method_1替换为Mock对象,并指定该Mock对象返回自定义的值,测试代码如下:
from unittest.mock import Mock import unittest class TestSomeClass(unittest.TestCase): some_object=Some_Class() some_object.some_method_1 = Mock(return_value=foo) self.assertEqual(an_expected_value, some_object.some_method_2())
上述代码实际运行完全正常,无执行报错,测试结果也符合预期。
Mypy说明:Mypy是Python的静态类型检查工具,可基于类型注解、变量赋值规则、库存根文件等标准校验代码的类型使用是否正确,整个校验过程无需解释执行实际代码。
使用Mypy检查上述代码时,针对如下代码行抛出错误:
some_object.some_method_1 = Mock(return_value=foo)
错误提示表明Python中不允许将对象赋值给callable。已知Mypy有时会出现误报(即提示的问题在实际运行中不存在),且当前代码确实可以无错运行,无法确定该报错是代码写法存在问题,还是属于Mypy的误报。如果写法有误,需要明确正确实现该单元测试场景的方式。
这个报错不是Mypy误报。直接给实例动态绑定方法替换原有类成员的写法虽然在运行时可以生效,但违反了Python静态类型检查的默认约定,Mypy无法推断这种动态修改的类型合法性。另外上述测试代码本身也不符合unittest的编写规范——测试逻辑不能直接写在测试类的类体中,必须放在test_开头的实例方法里,否则测试框架不会按预期执行用例。
有两种合规的实现方式,可以同时满足运行逻辑和Mypy校验要求:
- 优先使用
unittest.mock提供的标准patch.object接口替换目标方法,这是单元测试场景下的官方推荐写法,Mypy可以完整识别其类型逻辑,不需要额外加忽略标记:
from unittest.mock import Mock, patch import unittest class TestSomeClass(unittest.TestCase): def test_some_method_2(self): some_object = SomeClass() # 用上下文管理器替换指定实例的目标方法 with patch.object(target=some_object, attribute="some_method_1", return_value=foo) as mock_method1: actual_res = some_object.some_method_2() self.assertEqual(an_expected_value, actual_res) # 可选:校验mock方法是否按预期被调用 mock_method1.assert_called_once()
如果不需要上下文管理器的自动恢复逻辑,也可以通过patch.object装饰器在测试方法层面完成替换,效果一致。
- 如果一定要保留直接赋值的写法,可以在对应代码行加类型忽略标记,明确告知Mypy跳过该行的赋值类型校验,这种方式仅适合临时快速修复,不推荐在长期维护的代码中使用:
some_object.some_method_1 = Mock(return_value=foo) # type: ignore[assignment]
内容的提问来源于stack exchange,提问作者Bornak

