3

在 Python 中,如果你调用一个不存在的方法,它会抛出一个 AttributeError。前任

>>> class A:
...     def yo(self):
...             print(1)
... 
>>> a = A()
>>> a.yo()
1
>>> a.hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hello'

在下面的代码中,MagicMock 类没有名为 hello 的函数,或者没有为方法 hello 创建补丁。仍然下面的代码不会抛出 AttributeError

>>> from unittest.mock import MagicMock 
>>> obj = MagicMock()
>>> obj.hello()
<MagicMock name='mock.hello()' id='4408758568'>

MagicMock 是如何做到这一点的?当调用任何方法(可能未定义)时,如何创建一个可以执行操作的类?

4

2 回答 2

2

Python 数据模型记录了一个钩子,__getattr__当属性访问无法以通常的方式解析时,将调用该钩子。模拟使用它返回一个新的模拟实例——即模拟将未知属性定义为工厂

以更简单的方式重现 mock 的实现,您只需将__getattr____call__转换为工厂函数:

class M:
    def __call__(self):
        return M()
    def __getattr__(self, name):
        return M()

示例用法:

>>> mock = M()
>>> mock.potato
<__main__.M at 0xdeadbeef>
>>> mock.potato()
<__main__.M at 0xcafef00d>

MagicMock 是如何做到这一点的?

这部分不是特定的MagicMock,普通人Mock也会这样做(名称中的“魔术”只是指允许更好地模拟魔术方法的附加功能)。 MagicMock 从基类之一继承此类行为

>>> MagicMock.mro()
[unittest.mock.MagicMock,
 unittest.mock.MagicMixin,
 unittest.mock.Mock,
 unittest.mock.CallableMixin,
 unittest.mock.NonCallableMock,  # <--- this one overrides __getattr__!
 unittest.mock.Base,
 object]

当调用任何方法(可能未定义)时,如何创建一个可以执行操作的类?

这取决于您是想在普通属性访问之前还是之后。如果你想走在前面,你应该定义__getattribute__,它在搜索类/实例命名空间之前被无条件地调用以实现属性访问。但是,如果您希望降低普通属性(即存在于对象中的属性__dict__)和描述符的优先级,那么您应该__getattr__按照之前讨论的方式进行定义。

于 2018-10-24T23:54:14.030 回答
1

我实际上并不知道具体是如何MagicMock工作的(我从未使用过它,但我听说过好东西),但这部分行为可以通过劫持来复制(可能还有其他多种可能的解决方案__getattr__)它返回一个可调用对象,在调用时创建一个新的模拟实例:

class MM:
    def __init__(self, name=None):
        # store a name, TODO: random id, etc.
        self.name = name

    def __repr__(self):
        # make it pretty
        if self.name:
            r = f'<MM name={self.name}>'
        else:
            r = f'<MM>'
        return r

    def __getattr__(self, attrname):
        # we want a factory for a mock instance with a name corresponding to attrname
        def magicattr():
            return MM(name=f"'mock.{attrname}()'")
        return magicattr

执行时,我们看到以下内容:

>>> MM()
<MM>
>>> MM().hello()
<MM name='mock.hello()'>

我没有过分定义 anid和诸如此类的东西,但基本技巧可以在上面的精简示例中看到。

上面的工作方式是访问.hello或任何其他属性通过我们的自定义__getattr__,这使我们有机会动态生成一个假的(模拟的)方法,具有我们想要的任何属性。据我了解,它的众多好处之一MagicMock就是我们不必担心AttributeErrors 默认会被抛出,它可以正常工作

于 2018-10-24T22:36:27.777 回答