python mock: @wraps(f) 问题

12 投票
1 回答
5290 浏览
提问于 2025-04-17 20:58

我想测试我写的一个简单的装饰器:

它的样子是这样的:

#utilities.py
import other_module
def decor(f):
    @wraps(f)
    def wrapper(*args, **kwds):
            other_module.startdoingsomething()
        try:
            return f(*args, **kwds)
        finally:
            other_module.enddoingsomething()
    return wrapper

然后我用python-mock来测试它:

#test_utilities.py
def test_decor(self):
    mock_func = Mock()
    decorated_func = self.utilities.decor(mock_func)
    decorated_func(1,2,3)
    self.assertTrue(self.other_module.startdoingsomething.called)
    self.assertTrue(self.other_module.enddoingsomething.called)
    mock_func.assert_called_with(1,2,3)

但是它给我返回了:

Traceback (most recent call last):
  File "test_utilities.py", line 25, in test_decor
    decorated_func = Mock(wraps=self.utilities.decor(mock_func))
  File "utilities.py", line 35, in decor
    @wraps(f)
  File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 660, in __getattr__
    raise AttributeError(name)
AttributeError: __name__

我知道functools.wraps()只是一个辅助工具。所以如果我把它去掉,测试就能正常工作了。

我能让Mock和functools.wraps()一起正常工作吗?

Python 2.7.3

1 个回答

16

只需要给你的模拟对象加上那个属性就行:

mock_func.__name__ = 'foo'

就这么简单。

演示:

>>> from functools import wraps
>>> from mock import Mock
>>> def decor(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         return f(*args, **kwds)
...     return wrapper
... 
>>> mock_func = Mock()
>>> decor(mock_func)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in decor
  File ".../opt/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  File ".../lib/python2.7/site-packages/mock.py", line 660, in __getattr__
    raise AttributeError(name)
AttributeError: __name__
>>> mock_func.__name__ = 'foo'
>>> decor(mock_func)
<function foo at 0x10c4321b8>

设置 __name__ 是完全可以的;@wraps 装饰器会把 __name__ 属性复制到包装函数上,而在函数对象中,这个属性通常是一个字符串。无论如何,这个属性在函数上是可以写的,只要你使用字符串,function.__name__ 可以设置成任何值。

撰写回答