在添加了一个新的单元测试之后,我开始在新测试之后的无关测试运行中失败我不明白为什么。
我把这个案子简化为下面的代码。我还是不明白发生了什么事我很惊讶注释掉看似不相关的代码行会影响结果:删除对isinstance
中Block.__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_abc
tests 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=AddonDefault
、subclass=FBlock
和scls=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/