Python的isinstance方法结果对于子类实例是意外的

sku*_*zmo 2 python program-entry-point subclass isinstance

使用 Python 的 isinstance 方法时,定义两个类(两个单独的文件中的基类“ClassA”和子类“ClassB”)会产生意外结果。输出似乎受到运行时使用的模块名称(命名空间?)的影响(__main__)。此行为出现在 Python 3.8.5 和 3.10.4 上。

文件 ClassA.py 包含:

class ClassA:
    def __init__(self, id):
        self.id = id
    def __str__(self) -> str:
        class_name = type(self).__name__
        return f"{class_name} WITH id: {self.id}"

def main():
    from ClassB import ClassB
    id = 42
    for i, instance in enumerate([ClassA(id), ClassB(id)]):
        label = f"{type(instance).__name__}:"
        print("#" * 50)
        print(f"{label}   type: {type(instance)}")
        label = " " * len(label)  # Convert label to appropriate number of spaces
        is_a = isinstance(instance, ClassA)
        is_b = isinstance(instance, ClassB)
        print(f"{label} is_a/b: {is_a}/{is_b}")
        print(f"{label}    str: {instance}")

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

文件 ClassB.py 包含:

from ClassA import ClassA

class ClassB(ClassA):
    def __init__(self, id):
        super().__init__(id)
        self.id *= -1
Run Code Online (Sandbox Code Playgroud)

文件 main.py 包含:

if __name__ == "__main__":
    from ClassA import main
    main()
Run Code Online (Sandbox Code Playgroud)

运行 ClassA.py 的输出给出:

01: ##################################################
02: ClassA:   type: <class '__main__.ClassA'>
03:         is_a/b: True/False
04:            str: ClassA WITH id: 42
05: ##################################################
06: ClassB:   type: <class 'ClassB.ClassB'>
07:         is_a/b: False/True
08:            str: ClassB WITH id: -42
Run Code Online (Sandbox Code Playgroud)

运行 main.py(调用 ClassA.main)的输出给出:

01: ##################################################
02: ClassA:   type: <class 'ClassA.ClassA'>
03:         is_a/b: True/False
04:            str: ClassA WITH id: 42
05: ##################################################
06: ClassB:   type: <class 'ClassB.ClassB'>
07:         is_a/b: True/True
08:            str: ClassB WITH id: -42
Run Code Online (Sandbox Code Playgroud)

请注意 ClassA 实例的类型如何(在第 02 行)从“__main__.ClassA”(从 ClassA.py 运行时)更改为“ClassA.ClassA”(从 main.py 运行时)。同样,ClassA 和 ClassB 的 isinstance 类型检查(第 07 行)从“False/True”(意外)更改为“True/True”(期望、预期)。

任何评论/建议/解释都会有帮助。谢谢。

Sha*_*ger 5

您在这里遇到的问题是由于ClassA在主脚本中定义的,并且您有循环导入。实际上,您的脚本涉及三个模块,而不是两个:

  1. 主脚本的名称为__main__(defines __main__.ClassA),它导入...
  2. ClassB(定义ClassB.ClassB)导入...
  3. ClassA(定义ClassA.ClassA与 定义相同__main__.ClassA,但是一个唯一且独立的类)主脚本的完全独立的副本,但以不同的名称独立导入,因此__main__不会触发相关行为

重要的是,ClassB.ClassB继承自ClassA.ClassA,但针对完全不相关的类main进行类型检查。__main__.ClassA

我之前已经讨论过为什么这不能按预期工作(并且在另一个上下文中再次),因此我将针对您的具体情况将此处的答案简化为:不要涉及__main__任何循环导入。它可以导入任何它喜欢的东西,但不应该从中导入任何其他东西。在这种情况下,您的重构足以解决问题(通过确保 class ,main.py仅有一个版本)。它确实有一个循环导入依赖项,这总是有点奇怪(我建议移动该函数以避免这种情况),但由于您推迟了其中一个导入,所以它足够安全。ClassAClassA.ClassAmainmain.py