对于通过包和直接从同一模块导入的类型,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)
现在的问题是,a
是 lib.foo.Custom
类型,而 isinstance 调用将检查它是否等于 foo.Custom
,这显然是 returns 错误。
如何避免这个问题,而不必更改库 (lib) 中的任何内容?
当通过同一进程中的两个不同路径导入模块时 - 就像这里 foo.py
中的 import Types
和 main.py
中的 import lib.Types
一样,它确实被导入了两次,产生两个不同的模块对象,每个对象都有自己不同的函数和 class 实例(您可以使用 id(obj_or_class)
自行检查),有效地破坏了 is
和 isinstance
测试。
这里的解决方案是将 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
作为其中的子模块,创建模块对象 lib
和 lib.Types
在 sys.modules
.
- 当您导入
Types
时,它将 Types.py
作为独立模块导入,在 sys.modules
. 中创建模块对象 Types
因此,Types
和 lib.Types
最终成为两个不同的模块对象。 Python 不检查它们是否是同一个文件,以保持简单并避免事后猜测你。
(这实际上在 Traps for the Unwary in Python’s Import System 文章中列为 "double import trap"。)
如果从 PYTHONPATH
中删除 lib
,lib/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)
/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
类型,而 isinstance 调用将检查它是否等于 foo.Custom
,这显然是 returns 错误。
如何避免这个问题,而不必更改库 (lib) 中的任何内容?
当通过同一进程中的两个不同路径导入模块时 - 就像这里 foo.py
中的 import Types
和 main.py
中的 import lib.Types
一样,它确实被导入了两次,产生两个不同的模块对象,每个对象都有自己不同的函数和 class 实例(您可以使用 id(obj_or_class)
自行检查),有效地破坏了 is
和 isinstance
测试。
这里的解决方案是将 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
作为其中的子模块,创建模块对象lib
和lib.Types
在sys.modules
. - 当您导入
Types
时,它将Types.py
作为独立模块导入,在sys.modules
. 中创建模块对象
Types
因此,Types
和 lib.Types
最终成为两个不同的模块对象。 Python 不检查它们是否是同一个文件,以保持简单并避免事后猜测你。
(这实际上在 Traps for the Unwary in Python’s Import System 文章中列为 "double import trap"。)
如果从 PYTHONPATH
中删除 lib
,lib/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)