对于通过包和直接从同一模块导入的类型,isinstance 失败

isinstance fails for a type imported via package and from the same module directly

/Project
|-- main.py
|--/lib
|  |--__init__.py
|  |--foo.py
|  |--Types.py

/Project/lib 已添加到 PYTHONPATH 变量。

Types.py:

class Custom(object):
    def __init__(self):
        a = 1
        b = 2

foo.py:

from Types import Custom
def foo(o):
    assert isinstance(o, Custom)

最后,来自main.py

from lib.Types import Custom
from lib.foo import foo
a = Custom()
foo(a)

现在的问题是,alib.foo.Custom 类型,而 isinstance 调用将检查它是否等于 foo.Custom,这显然是 returns 错误。

如何避免这个问题,而不必更改库 (lib) 中的任何内容?

当通过同一进程中的两个不同路径导入模块时 - 就像这里 foo.py 中的 import Typesmain.py 中的 import lib.Types 一样,它确实被导入了两次,产生两个不同的模块对象,每个对象都有自己不同的函数和 class 实例(您可以使用 id(obj_or_class) 自行检查),有效地破坏了 isisinstance 测试。

这里的解决方案是将 Project(而不是 Project/lib)添加到你的 pythonpath(顺便说一句,无论如何都应该这样做 - pythonpath/sys.path 应该是一个列表目录 包含 包和模块,而不是包目录本身)并在所有地方使用 from lib.Type import Custom,因此您只有一个模块实例。

你不应该同时制作 lib 一个包并将其添加到 PYTHONPATH。这使得可以将其模块作为 lib. 和直接导入,从而为失败做好准备。

如您所见,

lib.Types.Custom != Types.Custom

因为 the way Python imports work.

Python 搜索导入路径并解析它找到的适当条目。

  • 当您导入 lib.Types 时,它会将 lib 目录作为包导入,然后 lib/Types.py 作为其中的子模块,创建模块对象 liblib.Typessys.modules.
  • 当您导入 Types 时,它将 Types.py 作为独立模块导入,在 sys.modules.
  • 中创建模块对象 Types

因此,Typeslib.Types 最终成为两个不同的模块对象。 Python 不检查它们是否是同一个文件,以保持简单并避免事后猜测你。

(这实际上在 Traps for the Unwary in Python’s Import System 文章中列为 "double import trap"。)


如果从 PYTHONPATH 中删除 liblib/foo.py 中的导入需要成为相对导入:

from .Types import Custom

或绝对导入:

from lib.Types import Custom
# For generating a class UUID: uuidgen -n "<MODULE_UUID>" -N <Python class name> -s
# Example: uuidgen -n "dec9b2e9-07c0-4f59-af97-92f171e6fe33" -N Args -s
MODULE_UUID = "dec9b2e9-07c0-4f59-af97-92f171e6fe33"

def get_class_uuid(obj_or_cls):
    if isinstance(obj_or_cls, type):
        # it's a class
        return getattr(obj_or_cls, "CLASS_UUID", None)
    # it's an object
    return getattr(obj_or_cls.__class__, "CLASS_UUID", None)

def same_type(obj, cls):
    return get_class_uuid(obj) == get_class_uuid(cls)

class Foo:
    CLASS_UUID = "340637d8-5cb7-53b1-975e-d3f30bb825cd"

    @staticmethod
    def check_type(obj, accept_none=True):
        if obj is None:
            return accept_none 
        return same_type(obj, Foo)
...

assert Foo.check_type(obj)