英文:
Child Class from MagicMock object has weird spec='str' and can't use or mock methods of the class
问题
当从一个继承自MagicMock()
对象的类创建时,它会具有不希望的spec='str'
。有人知道为什么会发生这种情况吗?有人知道在这种情况下可以对MagicMock()
对象进行哪些操作,以使其不具有spec='str'
,或者可以使用类的方法吗?
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'
基本上,我需要避免AttributeError
。有什么方法可以使c.x()
有效吗?
编辑 - 问题似乎与mock.py中的_mock_add_spec
有关,但我仍然不确定如何修复这个问题。
英文:
When a class is created deriving from a MagicMock() object it has an unwanted spec='str'. Does anyone know why this happens? Does anyone know any operations that could be done to the MagicMock() object in this case such that it doesn't have the spec='str' or can use methods of the class?
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())
which returns
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'
Basically I need the AttributeError to not be here. Is there something I can do to 'a' such that c.x() is valid?
edit - the issue seems to be with _mock_add_spec in mock.py still not sure how to fix this.
答案1
得分: 1
在Python中,类实际上是type
类的实例。像这样的class
语句:
class c(a):
@staticmethod
def x():
return 1
实际上是对使用类名、基类和类成员调用type
的一种语法糖:
c = type('c', (a,), {'x': staticmethod(lambda: 1)})
上面的语句将遍历给定的基类,并调用第一个定义了__new__
方法的基类的类型的__new__
方法,这种情况下是a
。返回值将分配给c
,以创建一个新的类。
通常情况下,a
将是一个实际的类,即type
的实例或type
的子类的实例。但在这种情况下,a
不是type
的实例,而是MagicMock
的实例,因此将使用这三个参数来调用MagicMock.__new__
,而不是type.__new__
。
问题在于:MagicMock
不是type
的子类,因此其__new__
方法不打算接受与type.__new__
相同的参数。然而,当使用这三个参数调用MagicMock.__new__
时,它却毫不犹豫地接受了这些参数,因为根据MagicMock
构造函数的签名(与Mock
相同):
class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)
MagicMock.__new__
将把这三个位置参数分配为spec
、side_effect
和return_value
。正如你现在所看到的,第一个参数,类名(在这种情况下为'c'
),成为spec
,这就是为什么你的类c
成为了一个具有str
作为spec
的MagicMock
实例。
解决方案
幸运的是,自Python 3.7引入了一个名为__mro_entries__
的魔术方法,它可以通过为非类基类提供替代基类来解决这个问题,因此当a
作为基类时,可以使用__mro_entries__
来强制其子类使用a
的类MagicMock
(或以下示例中的SubclassableMagicMock
)作为基类:
from unittest.mock import MagicMock
class SubclassableMagicMock(MagicMock):
def __mro_entries__(self, bases):
return self.__class,
因此:
a = SubclassableMagicMock()
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())
输出:
<SubclassableMagicMock id='140127365021408'>
<class '__main__.b'>
<class '__main__.c'>
<SubclassableMagicMock name='mock.x()' id='140127351680080'>
1
1
演示:https://replit.com/@blhsing/HotAcademicCases
英文:
In Python, classes are actually instances of the type
class. A class
statement like this:
class c(a):
@staticmethod
def x():
return 1
is really syntactic sugar of calling type
with the name of the class, the base classes and the class members:
c = type('c', (a,), {'x': staticmethod(lambda: 1)})
The above statement would go through the given base classes and call the __new__
method of the type of the first base class with the __new__
method defined, which in this case is a
. The return value gets assigned to c
to become a new class.
Normally, a
would be an actual class--an instance of type
or a subclass of type
. But in this case, a
is not an instance of type
, but rather an instance of MagicMock
, so MagicMock.__new__
, instead of type.__new__
, is called with these 3 arguments.
And here lies the problem: MagicMock
is not a subclass of type
, so its __new__
method is not meant to take the same arguments as type.__new__
. And yet, when MagicMock.__new__
is called with these 3 arguments, it takes them without complaint anyway because according to the signature of MagicMock
's constructor (which is the same as Mock
's):
> class unittest.mock.Mock(spec=None, side_effect=None,
> return_value=DEFAULT, wraps=None, name=None, spec_set=None,
> unsafe=False, **kwargs)
MagicMock.__new__
would assign the 3 positional arguments as spec
, side_effect
and return_value
, respectively. As you now see, the first argument, the class name ('c'
in this case), an instance of str
, becomes spec
, which is why your class c
becomes an instance of MagicMock
with a spec
of str
.
The solution
Luckily, a magic method named __mro_entries__
was introduced since Python 3.7 that can solve this problem by providing a non-class base class with a substitute base class, so that when a
, an instance of MagicMock
, is used as a base class, we can use __mro_entries__
to force its child class to instead use a
's class, MagicMock
(or SubclassableMagicMock
in the following example), as a base class:
from unittest.mock import MagicMock
class SubclassableMagicMock(MagicMock):
def __mro_entries__(self, bases):
return self.__class__,
so that:
a = SubclassableMagicMock()
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())
outputs:
<SubclassableMagicMock id='140127365021408'>
<class '__main__.b'>
<class '__main__.c'>
<SubclassableMagicMock name='mock.x()' id='140127351680080'>
1
1
答案2
得分: -2
有趣...
您遇到的问题是因为MagicMock对象具有"spec"属性,该属性仅允许属于指定对象的方法。在这种情况下,"spec"被设置为"str",因此只允许"str"类的方法。
要解决这个问题,您可以选择创建不带"spec"属性的MagicMock对象,或将"spec"属性设置为"None",这将允许所有方法:
from unittest.mock import MagicMock
a = MagicMock(spec=None) # 或者 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())
这样应该允许调用"c.x()"方法而不引发"AttributeError"。同时要注意配置不正确的模拟对象!
英文:
Interesting...
The issue you're facing is because the MagicMock object has a "spec" attribute, which only allows methods that are part of the specified object. In this case, the "spec" is set to "str", so only methods of the "str" class are allowed.
To resolve this, you can either create the MagicMock object without a "spec" attribute, or set the "spec" attribute to "None", which will allow all methods:
from unittest.mock import MagicMock
a = MagicMock(spec=None) # or 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())
This should allow the "c.x()" method to be called without raising an "AttributeError". Also be careful of misconfigured mocks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论