继承MagicMock的子类意外带spec='str',无法调用类方法如何解决?
问题
当从MagicMock()对象派生创建类时,该类会意外带有spec='str'属性,导致子类无法调用自身定义的类方法(如示例中的c.x()抛出AttributeError)。请问这一现象的原因是什么?如何操作MagicMock()对象,使其不带有该属性,或让子类能够正常调用自身的类方法?
测试代码
from unittest.mock import MagicMock a = MagicMock() class b(): @staticmethod def x(): return 1 class c(a): @staticmethod def x(): return 1 print(a) print(b) print(c) print(a.x()) print(b.x()) print(c.x())
运行输出
MagicMock id='140670188364408'> <class '__main__.b'> <MagicMock spec='str' id='140670220499320'> <MagicMock name='mock.x()' id='140670220574848'> 1 Traceback (most recent call last): File "/xyz/test.py", line 19, in <module> print(c.x()) File "/xyz/lib/python3.7/unittest/mock.py", line 580, in _getattr_ raise AttributeError("Mock object has no attribute %r" % name) AttributeError: Mock object has no attribute 'x'
原因分析
这一现象的核心是Python类继承机制与MagicMock的特殊行为冲突:
- 当你将
MagicMock实例a作为父类创建子类c时,Python会触发a的__class_getitem__方法(类继承涉及元类层面的对象创建操作)。 MagicMock默认会将未定义的方法调用转发给内部mock逻辑,处理类继承的特殊操作时,它错误地将自身的spec设置为str——这是因为MagicMock在处理部分特殊属性/方法时,默认用字符串的spec生成mock对象,最终导致子类c变成了带有strspec的MagicMock实例,而非普通自定义类。- 带有
strspec的mock会严格限制可访问属性,仅允许字符串原生的方法/属性,因此自定义的x()方法会被mock的属性拦截逻辑拒绝,抛出AttributeError。
解决方法
有三种可行方案消除该错误:
方案1:继承MagicMock类而非实例
不要用MagicMock的实例作为父类,直接继承MagicMock类本身,这样创建的子类是正常自定义类,不会被mock的spec逻辑干扰:
from unittest.mock import MagicMock class b(): @staticmethod def x(): return 1 # 直接继承MagicMock类而非实例 class c(MagicMock): @staticmethod def x(): return 1 print(c.x()) # 输出:1
方案2:显式指定MagicMock的spec参数
若必须用MagicMock实例作为父类,创建MagicMock时显式指定spec为object(或其他不限制自定义属性的类型),避免默认生成str spec:
from unittest.mock import MagicMock # 显式指定spec为object,规避默认的str spec限制 a = MagicMock(spec=object) class b(): @staticmethod def x(): return 1 class c(a): @staticmethod def x(): return 1 print(c.x()) # 输出:1
方案3:重写MagicMock的属性拦截逻辑
通过修改MagicMock的__getattr__方法,让它优先返回子类自身定义的属性:
from unittest.mock import MagicMock a = MagicMock() # 重写__getattr__,优先检查子类是否存在目标属性 original_getattr = a.__getattr__ def custom_getattr(name): if name in dir(type(a)): return getattr(type(a), name) return original_getattr(name) a.__getattr__ = custom_getattr class c(a): @staticmethod def x(): return 1 print(c.x()) # 输出:1
内容的提问来源于stack exchange,提问作者arrmansa
相关产品推荐
相关产品推荐

