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

时间:2017-10-12 11:39:55

标签: python python-3.x python-3.4

/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)

现在的问题是,a的类型为lib.foo.Custom,而实例调用将检查它是否等于foo.Custom,这显然会返回false。

如何在不更改库(lib)中的任何内容的情况下避免此问题?

3 个答案:

答案 0 :(得分:8)

您不应同时将lib打包并将其添加到PYTHONPATH。这样就可以将其模块同时导入lib.并直接导入失败。

如您所见,

lib.Types.Custom != Types.Custom

因为the way Python imports work

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

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

因此,sys.modulesTypes最终成为两个不同的模块对象。 Python不会检查它们是否是同一个文件,以保持简单并避免再次猜测。

(这实际上在Traps for the Unwary in Python’s Import System文章中列为“双重导入陷阱”。)

如果您从lib.Types移除libPYTHONPATH中的导入将需要成为相对导入:

lib/foo.py

或绝对导入:

from .Types import Custom

答案 1 :(得分:3)

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

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

答案 2 :(得分:0)

# 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)