MagicMock 如何在调用随机方法时避免抛出 AttributeError?
How does MagicMock avoid throwing AttributeError when a random method is called?
在 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 class 没有名为 hello 的函数,或者没有为方法 hello 创建补丁。仍然在下面的代码不会抛出 AttributeError
>>> from unittest.mock import MagicMock
>>> obj = MagicMock()
>>> obj.hello()
<MagicMock name='mock.hello()' id='4408758568'>
MagicMock 如何做到这一点?我如何创建一个 class 可以在调用任何方法(可能未定义)时执行操作?
我实际上并不知道 MagicMock
具体如何工作(我从未使用过它,但我听说过好事),但这部分行为可以复制(可能还有多个)其他可能的解决方案)通过劫持 __getattr__
的方式,它 returns 一个可调用的,在被调用时创建一个新的模拟实例:
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()'>
我并没有过分定义 id
之类的东西,但是可以在上面的精简示例中看到基本技巧。
上面的工作方式是访问 .hello
或任何其他属性通过我们的自定义 __getattr__
这让我们有机会动态生成一个假的(模拟的)方法,无论我们想要的属性。据我了解,MagicMock
的众多好处之一就是我们不必担心 AttributeError
会被默认抛出,它 可以正常工作 。
Python 数据模型记录了一个钩子,__getattr__
, which shall be called when attribute access fails to resolve in the usual ways. Mocks use it to return a new mock instance - i.e. mocks define unknown attributes as factories。
以更简单的方式复制 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>
How is MagicMock able to do this?
这部分并不特定于 MagicMock
,一个普通的 Mock
也会做同样的事情(名称中的 "magic" 只是指允许更好地模拟 magic methods). MagicMock
inherits such behavior from one of the base classes:
>>> 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]
How can I create a class that can perform an action when any method (which might not be defined) is called on it?
这取决于您是想在普通属性访问之前还是之后。如果你想排在前面,你应该定义__getattribute__
, it's called unconditionally to implement attribute accesses before searching the class/instance namespaces. However, if you want to take a lower precedence to normal attributes (i.e. those living in the object __dict__
) and to descriptors,然后你应该像前面讨论的那样定义__getattr__
。
在 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 class 没有名为 hello 的函数,或者没有为方法 hello 创建补丁。仍然在下面的代码不会抛出 AttributeError
>>> from unittest.mock import MagicMock
>>> obj = MagicMock()
>>> obj.hello()
<MagicMock name='mock.hello()' id='4408758568'>
MagicMock 如何做到这一点?我如何创建一个 class 可以在调用任何方法(可能未定义)时执行操作?
我实际上并不知道 MagicMock
具体如何工作(我从未使用过它,但我听说过好事),但这部分行为可以复制(可能还有多个)其他可能的解决方案)通过劫持 __getattr__
的方式,它 returns 一个可调用的,在被调用时创建一个新的模拟实例:
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()'>
我并没有过分定义 id
之类的东西,但是可以在上面的精简示例中看到基本技巧。
上面的工作方式是访问 .hello
或任何其他属性通过我们的自定义 __getattr__
这让我们有机会动态生成一个假的(模拟的)方法,无论我们想要的属性。据我了解,MagicMock
的众多好处之一就是我们不必担心 AttributeError
会被默认抛出,它 可以正常工作 。
Python 数据模型记录了一个钩子,__getattr__
, which shall be called when attribute access fails to resolve in the usual ways. Mocks use it to return a new mock instance - i.e. mocks define unknown attributes as factories。
以更简单的方式复制 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>
How is MagicMock able to do this?
这部分并不特定于 MagicMock
,一个普通的 Mock
也会做同样的事情(名称中的 "magic" 只是指允许更好地模拟 magic methods). MagicMock
inherits such behavior from one of the base classes:
>>> 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]
How can I create a class that can perform an action when any method (which might not be defined) is called on it?
这取决于您是想在普通属性访问之前还是之后。如果你想排在前面,你应该定义__getattribute__
, it's called unconditionally to implement attribute accesses before searching the class/instance namespaces. However, if you want to take a lower precedence to normal attributes (i.e. those living in the object __dict__
) and to descriptors,然后你应该像前面讨论的那样定义__getattr__
。