【问题标题】:Mock modules and subclasses (TypeError: Error when calling the metaclass bases)模拟模块和子类(TypeError:调用元类库时出错)
【发布时间】:2016-07-14 14:47:15
【问题描述】:

要在 readthedocs 上编译文档,必须模拟模块 h5py。我得到一个可以用这个简单的代码重现的错误:

from __future__ import print_function
import sys

try:
    from unittest.mock import MagicMock
except ImportError:
    # Python 2
    from mock import Mock as MagicMock


class Mock(MagicMock):
    @classmethod
    def __getattr__(cls, name):
        return Mock()

sys.modules.update({'h5py': Mock()})

import h5py
print(h5py.File, type(h5py.File))


class A(h5py.File):
    pass

print(A, type(A))


class B(A):
    pass

这个脚本的输出是:

<Mock id='140342061004112'> <class 'mock.mock.Mock'>
<Mock spec='str' id='140342061005584'> <class 'mock.mock.Mock'>
Traceback (most recent call last):
  File "problem_mock.py", line 32, in <module>
class B(A):
TypeError: Error when calling the metaclass bases
    str() takes at most 1 argument (3 given)

模拟h5pyh5py.File 的正确方法是什么?

在我看来,这个问题对于带有 readthedocs 的文档来说非常普遍,其中一些模块必须被模拟。社区有一个答案会很有用。

【问题讨论】:

  • 您在 Python 2 中使用 Mock,在 Python 3 中使用 MagicMock。您将在 Python 3 中获得无限递归只是尝试创建您的实例子类。因此,您的示例仅适用于 Python 2。您不需要创建子类只是为了让 __getattr__ 返回更多 MagicMock 实例,但这是默认行为.

标签: python mocking read-the-docs


【解决方案1】:

你不能真正使用Mock instances 来充当;它在 Python 2 上失败了,并且只能在 Python 3 上运行(见下文)。

如果您希望它们在类层次结构中工作,则必须返回 Mock class 本身:

>>> class A(Mock):  # note, not called!
...     pass
...
>>> class B(A):
...     pass
...
>>> B
<class '__main__.B'>
>>> B()
<B id='4394742480'>

如果您根本无法导入 h5py,这意味着您需要保留一个手动更新的类列表,您需要在其中返回类而不是实例:

_classnames = {
    'File',
    # ...
}

class Mock(MagicMock):
    @classmethod
    def __getattr__(cls, name):
        return Mock if name in _classnames else Mock()

这不是万无一失的;无法在类方法中检测父实例,因此h5py.File().File 将导致返回另一个“类”,即使在实际实现中是其他对象。您可以部分通过创建一个新的描述符来解决这个问题,而不是classmethod 装饰器,该装饰器将绑定到类到一个可用的实例;这样,您至少会在 Mock 类的实例上拥有 self._mock_name 形式的上下文。


在 Python 3 中,直接使用 MagicMock 无需进一步定制,当用作基类时:

>>> from unittest.mock import MagicMock
>>> h5py = MagicMock()
>>> class A(h5py.File): pass
...
>>> class B(A): pass
...

但这并不是真正的有意和支持的行为;类和子类是从类名字符串中“指定”的:

>>> A
<MagicMock spec='str' id='4353980960'>
>>> B
<MagicMock spec='str' id='4354132344'>

因此在实例化不起作用时会出现各种问题:

>>> A()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 917, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 976, in _mock_call
    result = next(effect)
StopIteration

【讨论】:

    猜你喜欢
    • 2016-07-03
    • 2021-04-28
    • 2015-10-16
    • 2016-09-22
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 2012-07-24
    • 1970-01-01
    相关资源
    最近更新 更多