简体   繁体   English

模拟模块和子类(TypeError:调用元类库时出错)

[英]Mock modules and subclasses (TypeError: Error when calling the metaclass bases)

To compile documentation on readthedocs, the module h5py has to be mocked. 要编译readthedocs上的文档,必须模拟模块h5py。 I get an error which can be reproduced with this simple code: 我收到一个错误,可以使用这个简单的代码重现:

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

The output of this script is: 该脚本的输出是:

<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)

What is the correct way to mock h5py and h5py.File ? 模拟h5pyh5py.File的正确方法是什么?

It seems to me that this issue is quite general for documentation with readthedocs where some modules have to be mocked. 在我看来,这个问题对于使用readthedocs的文档非常普遍,其中一些模块必须被模拟。 It would be useful for the community to have an answer. 社区有一个答案是有用的。

You can't really use Mock instances to act as classes ; 你不能真正使用Mock 实例作为 ; it fails hard on Python 2, and works by Python 3 only by accident (see below). 它在Python 2上很难实现,并且只是偶然地在Python 3中工作(见下文)。

You'd have to return the Mock class itself instead if you wanted them to work in a class hierarchy: 如果您希望它们在类层次结构中工作,则必须返回Mock 本身:

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

If you can't import h5py at all , that means you'll need to keep a manually updated list of classes where you return the class rather than an instance: 如果您无法导入h5py 可言 ,这意味着你需要保持在您返回类而不是一个实例类手动更新列表:

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

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

This is not foolproof; 这不是万无一失的; there is no way to detect the parent instance in a classmethod, so h5py.File().File would result in yet another 'class' being returned even if in the actual implementation that was some other object instead. 没有办法在classmethod中检测父实例,所以h5py.File().File会导致返回另一个'class',即使在实际的实现中是其他对象。 You could partially work around that by creating a new descriptor to use instead of the classmethod decorator that would bind to either the class or to an instance if one is available; 您可以通过创建一个新的描述符来代替classmethod装饰器来部分解决这个问题,该类classmethod装饰器将绑定到类实例(如果有的话); that way you at least would have a context in the form of self._mock_name on instances of your Mock class. 这样你至少会在你的Mock类的实例上有self._mock_name形式的上下文。


In Python 3, using MagicMock directly without further customisation works when used as a base class: 在Python 3中,当用作基类时, 直接使用MagicMock而无需进一步定制:

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

but this is not really intentional and supported behaviour; 但这不是真正有意和支持的行为; the classes and subclasses are 'specced' from the classname string: 从类名字符串中“引用”类和子类:

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

and thus have all sorts of issues down the line as instanciation doesn't work: 由于实例不起作用,因此有各种各样的问题:

>>> 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM