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

Python 'del' raises AttributeError if called on mock on Python 3.6, but not on >=3.7

我正在使用 unittest.mock 来测试我的代码。如果对象不存在,我创建的函数会使用 hasattr.

向该对象添加一个属性

根据 Python 3.6 and newer versions 中的文档,我可以使用 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 库是否已更新,或者它可能是关于如何在 Python 版本之间传递函数的(非)预期行为。

如有任何见解,我们将不胜感激。

这已在 222d303 for issue bpo-20239

中修复

这已在 d358a8c which first appeared in python 3.7.3rc1

中移植到 3.7

此补丁对 mocks

上的 __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)