在添加了一个新的单元测试之后,我开始在新测试之后的无关测试运行中失败我不明白为什么。
我把这个案子简化为下面的代码。我还是不明白发生了什么事我很惊讶注释掉看似不相关的代码行会影响结果:删除对isinstanceBlock.__init__的调用会更改isinstance(blk, AddonDefault)test_addons的结果。

import abc

class Addon:
    pass

class AddonDefault(Addon, metaclass=abc.ABCMeta):
    pass

class Block:
    def __init__(self):
        isinstance(self, CBlock)

class CBlock(Block, metaclass=abc.ABCMeta):
    def __init_subclass__(cls, *args, **kwargs):
        if issubclass(cls, Addon):
            raise TypeError("Do not mix Addons and CBlocks!")
        super().__init_subclass__(*args, **kwargs)

class FBlock(CBlock):
    pass

def test_addons():
    try:
        class CBlockWithAddon(CBlock, AddonDefault):
            pass
    except TypeError:
        pass

    blk = FBlock()
    assert not isinstance(blk, AddonDefault), "TEST FAIL"
    print("OK")

test_addons()

当我运行python3 test.py时,我得到测试失败异常。但是FBlock来自CBlock,而Block来自AddonDefault。它怎么可能是的一个实例?
更新:我想强调的是,发布代码的唯一目的是演示我无法理解的行为它是通过尽可能减少一个更大的程序创建的在这个过程中,它以前的任何逻辑都被丢失了,所以请照原样去做,并关注为什么它给出了一个明显错误的答案。

最佳答案

不是一个完整的答案,而是一些提示。
似乎CBlockWithAddon仍然被视为AddonDefault的一个子类例如,在test_addons()中添加两个打印语句:

def test_addons():
    print(AddonDefault.__subclasses__())
    try:
        class CBlockWithAddon(CBlock, AddonDefault):
            pass
    except TypeError:
        pass

    print(AddonDefault.__subclasses__())
    blk = FBlock()
    assert not isinstance(blk, AddonDefault), "TEST FAIL"
    print("OK")

结果
[]
[<class '__main__.test_addons.<locals>.CBlockWithAddon'>]
...
AssertionError: TEST FAIL

_py_abctests for this
    # Check if it's a subclass of a subclass (recursive)
    for scls in cls.__subclasses__():
        if issubclass(subclass, scls):
            cls._abc_cache.add(subclass)
            return True

cls=AddonDefaultsubclass=FBlockscls=CBlockWithAddon时,返回True。
所以看来有两件事出了问题:
创建不正确的cblockwithaddon仍然被视为addondefault的一个子类。
但cblockWithAddon的创建方式似乎是fblock的一个超类。
也许坏掉的cblockwithaddon实际上与cblock相同,因此是fblock的一个超类。
这对我来说已经足够了。也许这有助于你的调查。
(我必须使用import _py_abc as abc进行分析这似乎无关紧要。)
编辑1:我对其超类的直觉似乎是正确的:
CBWA = AddonDefault.__subclasses__()[0]
print(CBWA)
print(CBWA.__dict__.keys())
print(CBlock.__dict__.keys())
print(CBWA._abc_cache is CBlock._abc_cache)

给予
<class '__main__.test_addons.<locals>.CBlockWithAddon'>
dict_keys(['__module__', '__doc__'])
dict_keys(['__module__', '__init_subclass__', '__doc__', '__abstractmethods__', '_abc_registry', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version'])
True

因此,未正确创建CBlockWithAddon,例如,未正确设置其缓存注册表。因此,访问这些属性将访问(第一个)超级类的属性,在本例中是CBlock。创建CBlockWithAddon时,非虚拟CBlock将填充缓存,因为isinstance(self, CBlock)确实是blk的一个子类然后,当调用FBlock时,将不正确地重用此缓存。
我想这就回答了这个问题。现在,下一个问题是:为什么CBlock在从未被正确定义的情况下成为isinstance(blk, AddonDefault)的子类?
Edit2:概念的简单证明。
from abc import ABCMeta

class Animal(metaclass=ABCMeta):
    pass

class Plant(metaclass=ABCMeta):
    def __init_subclass__(cls):
        assert not issubclass(cls, Animal), "Plants cannot be Animals"

class Dog(Animal):
    pass

try:
    class Triffid(Animal, Plant):
        pass
except Exception:
    pass

print("Dog is Animal?", issubclass(Dog, Animal))
print("Dog is Plant?", issubclass(Dog, Plant))

会导致
Dog is Animal? True
Dog is Plant? True

请注意,更改打印报表的顺序将导致
Dog is Plant? False
Dog is Animal? False

关于python - 为什么此测试失败?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57848663/

10-09 22:48