Python 'del'如果在 Python 3.6 上对 mock 调用,则会引发 AttributeError,但在 >=3.7 上调用时不会



我使用unittest.mock来测试我的代码。如果一个对象不存在,我用hasattr为它添加一个属性。

根据Python 3.6和更新版本的文档,我可以使用del删除mock的属性,但它在Python 3.6上失败。在Python 3.7及以上版本中,它可以工作。

下面是一个简短的测试,显示了我要测试的内容:

from unittest.mock import Mock
def add_attribute(info):
if not hasattr(info, 'attribute'):
info.attribute = 'Attribute Added'
def test_mock():
info = Mock()
assert hasattr(info, 'attribute')
del info.attribute
assert not hasattr(info, 'attribute')
add_attribute(info)
assert info.attribute == 'Attribute Added'
del info.attribute
assert not hasattr(info, 'attribute')

在python 3.8中,它工作:

======= test session starts ========
platform linux -- Python 3.8.5, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /code
plugins: cov-2.12.1
collected 1 item                                                                                                                                                                                                         
test.py .  

在python 3.6中,它失败:

============================= test session starts ==============================
platform linux -- Python 3.6.14, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /code
plugins: cov-2.12.1
collected 1 item                                                               
test.py F                                          [100%]
=================================== FAILURES ===================================
__________________________________ test_mock ___________________________________
def test_mock():
info = Mock()
assert hasattr(info, 'attribute')
del info.attribute
assert not hasattr(info, 'attribute')
add_attribute(info)
assert info.attribute == 'Attribute Added'
>       del info.attribute
test.py:14: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <Mock id='140171347130128'>, name = 'attribute'
def __delattr__(self, name):
if name in _all_magics and name in type(self).__dict__:
delattr(type(self), name)
if name not in self.__dict__:
# for magic methods that are still MagicProxy objects and
# not set on the instance itself
return

if name in self.__dict__:
object.__delattr__(self, name)

obj = self._mock_children.get(name, _missing)
if obj is _deleted:
>           raise AttributeError(name)
E           AttributeError: attribute
/usr/local/lib/python3.6/unittest/mock.py:728: AttributeError
=========================== short test summary info ============================
FAILED test.py::test_mock - AttributeError: attribute
============================== 1 failed in 0.11s ===============================
test_python exited with code 1

我不确定unittest库是否更新了,或者可能是(un)预期的对象在Python版本之间传递函数的行为。

请赐教。

在222d303中修复了bpo-20239的问题

这在d358a8c中被反向移植到3.7,d358a8c首次出现在python 3.7.3rc1

这个补丁对mock上的__delattr__方法进行了重大更改

~ <3.7.3代码节选:

def __delattr__(self, name):
# ...
if name in self.__dict__:
object.__delattr__(self, name)
obj = self._mock_children.get(name, _missing)
if obj is _deleted:
raise AttributeError(name)

并与3.7.3+代码进行比较:

def __delattr__(self, name):
# ...
obj = self._mock_children.get(name, _missing)
if name in self.__dict__:
_safe_super(NonCallableMock, self).__delattr__(name)
elif obj is _deleted:
raise AttributeError(name)

在重复删除的情况下,3.6代码将触发AttributeError(由于第一个删除设置_deleted)—但是修复使得它可以避免(通过elif)

最新更新