从MagicMock对象继承的子类具有奇怪的规范’str’,无法使用或模拟类的方法。

huangapple go评论66阅读模式
英文:

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=&#39;140670188364408&#39;&gt;
&lt;class &#39;__main__.b&#39;&gt;
&lt;MagicMock spec=&#39;str&#39; id=&#39;140670220499320&#39;&gt;
&lt;MagicMock name=&#39;mock.x()&#39; id=&#39;140670220574848&#39;&gt;
1
Traceback (most recent call last):
    File &quot;/xyz/test.py&quot;, line 19, in &lt;module&gt;
        print(c.x())
    File &quot;/xyz/lib/python3.7/unittest/mock.py&quot;, line 580, in _getattr_
        raise AttributeError(&quot;Mock object has no attribute %r&quot; % name)
AttributeError: Mock object has no attribute &#39;x&#39;

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__将把这三个位置参数分配为specside_effectreturn_value。正如你现在所看到的,第一个参数,类名(在这种情况下为'c'),成为spec,这就是为什么你的类c成为了一个具有str作为specMagicMock实例。

解决方案

幸运的是,自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(&#39;c&#39;, (a,), {&#39;x&#39;: 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 (&#39;c&#39; 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:

&lt;SubclassableMagicMock id=&#39;140127365021408&#39;&gt;
&lt;class &#39;__main__.b&#39;&gt;
&lt;class &#39;__main__.c&#39;&gt;
&lt;SubclassableMagicMock name=&#39;mock.x()&#39; id=&#39;140127351680080&#39;&gt;
1
1

Demo: https://replit.com/@blhsing/HotAcademicCases

答案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!

huangapple
  • 本文由 发表于 2023年2月14日 01:39:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75439401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定