在不导入类型 Foo 的情况下确定对象是否为 Foo 类型
Determine if object is of type Foo without importing type Foo
假设我在文件中定义了一个 class:
import stuff
import more stuff
import stuff that takes a long time to import
class Foo(object):
def __init__(self, arg1, arg2 etc.):
self.arg1 = arg1
self.arg2 = arg2
# Lots of other stuff
# Lots of methods
在另一个文件中我有这段代码:
from big_file import Foo
def do_stuff(obj):
if isinstance(obj, Foo):
do_stuff
else:
do_other_stuff
假设由于我无法控制的原因,文件 Foo 需要很长时间才能导入。我如何重构此代码以不导入 Foo 但仍可靠地检查类型?我认为鸭子打字不适合我的特殊情况。
我应该例如检查 obj
的基数的字符串表示?还是有另一种更规范的方式?
通常,这将是 non-issue。如果你有一个 big_file.Foo
的实例,那么即使没有从你的其他文件中明确引用,它的父模块也已经在早些时候导入了。 Python 模块仅在第一次导入时加载一次(前提是您没有进行任何显式重新加载或弄乱 sys.modules
)。由于它已经被导入,在您的其他文件中执行 import big_file
应该立即 运行。
但是,如果您的其他文件只会在某些情况下遇到 big_file.Foo
并且 big_file
仅在实际需要时才在其他地方导入,那么您可以检查对象的 class 作为这样(这不支持subclasses):
def do_stuff(obj):
if (obj.__class__.__module__, obj.__class__.__name__) == ('big_file', 'Foo'):
do_stuff
else:
do_other_stuff
由于您已经指出 big_file.Foo
可以在应用程序的任何位置导入并且您希望支持子 classes,您可以检查它的模块是否已被导入并且有条件地检查类型。
import sys
def is_Foo(obj):
if 'big_file' in sys.modules:
return isinstance(obj, sys.modules['big_file'].Foo)
else:
return False
def do_stuff(obj):
if is_Foo(obj):
do_stuff
else:
do_other_stuff
如果 big_file
由于任何 non-standard 原因确实需要很长时间才能导入,您确实可以使用 str
表示。这是一个相当健壮的实现:
from big_file import Foo
def isFoo(obj):
try:
return obj.__module__ == 'big_file' and type(obj).__name__ == 'Foo'
except:
return False
print(isFoo(Foo(...)))
print(isFoo(42))
isFoo
函数测试传递的 obj
是否是在名为 big_file
的模块中定义的一些名为 Foo
的 class 的实例。如果您有多个具有相同名称的模块,这原则上可能会失败,例如在不同的包中,当然这对你来说很可能不是问题。
编辑,处理 Foo
的子classes
正如 sytech 所指出的,上述解决方案在 subclasses 上失败。也就是说,isFoo(obj)
returns False
如果 obj
是 Foo
的子 class 的实例,而 isinstance(obj, Foo)
returns True
。下面的代码是上面的通用版本,解决了这个问题:
import inspect
def isFoo(obj):
for cls in inspect.getmro(type(obj)):
try:
if cls.__module__ == 'big_file' and cls.__name__ == 'Foo':
return True
except:
pass
return False
这使用与以前相同的测试,但现在不仅针对 obj
的 class
,而且还针对其所有超级classes。
编辑,使想法失败证明
上面的唯一警告是我们只测试模块 name 而不是绝对路径。如前所述,只有当您的项目包含多个具有相同名称的模块时才会出现问题,其中包含相同名称的 classes。我们 可以 但是测试路径,当然这需要您在代码中指定模块的绝对路径:
import inspect
def my_isinstance(obj, classinfo):
if isinstance(classinfo[0], str):
classinfo = (classinfo, )
for module_path, cls_name in classinfo:
for cls in inspect.getmro(type(obj)):
try:
if inspect.getmodule(cls).__file__ == module_path and cls.__name__ == cls_name:
return True
except:
pass
return False
print(my_isinstance(Foo(1, 2), ('/path/to/big_file.py', 'Foo')))
print(my_isinstance(42, ('/path/to/big_file.py', 'Foo')))
为了使函数完全类似于内置 isinstance
,它现在还支持多个 class 作为输入(例如 tuple
形式 (('/path/to/module1.py', 'Foo'), ('/path/to/module2.py', 'Bar'))
,用于检查 obj
是否是 Foo
或 Bar
).
的实例
虽然这个版本是防弹的,但我个人更喜欢以前的isFoo
,因为指定模块的绝对路径有点难看。
假设我在文件中定义了一个 class:
import stuff
import more stuff
import stuff that takes a long time to import
class Foo(object):
def __init__(self, arg1, arg2 etc.):
self.arg1 = arg1
self.arg2 = arg2
# Lots of other stuff
# Lots of methods
在另一个文件中我有这段代码:
from big_file import Foo
def do_stuff(obj):
if isinstance(obj, Foo):
do_stuff
else:
do_other_stuff
假设由于我无法控制的原因,文件 Foo 需要很长时间才能导入。我如何重构此代码以不导入 Foo 但仍可靠地检查类型?我认为鸭子打字不适合我的特殊情况。
我应该例如检查 obj
的基数的字符串表示?还是有另一种更规范的方式?
通常,这将是 non-issue。如果你有一个 big_file.Foo
的实例,那么即使没有从你的其他文件中明确引用,它的父模块也已经在早些时候导入了。 Python 模块仅在第一次导入时加载一次(前提是您没有进行任何显式重新加载或弄乱 sys.modules
)。由于它已经被导入,在您的其他文件中执行 import big_file
应该立即 运行。
但是,如果您的其他文件只会在某些情况下遇到 big_file.Foo
并且 big_file
仅在实际需要时才在其他地方导入,那么您可以检查对象的 class 作为这样(这不支持subclasses):
def do_stuff(obj):
if (obj.__class__.__module__, obj.__class__.__name__) == ('big_file', 'Foo'):
do_stuff
else:
do_other_stuff
由于您已经指出 big_file.Foo
可以在应用程序的任何位置导入并且您希望支持子 classes,您可以检查它的模块是否已被导入并且有条件地检查类型。
import sys
def is_Foo(obj):
if 'big_file' in sys.modules:
return isinstance(obj, sys.modules['big_file'].Foo)
else:
return False
def do_stuff(obj):
if is_Foo(obj):
do_stuff
else:
do_other_stuff
如果 big_file
由于任何 non-standard 原因确实需要很长时间才能导入,您确实可以使用 str
表示。这是一个相当健壮的实现:
from big_file import Foo
def isFoo(obj):
try:
return obj.__module__ == 'big_file' and type(obj).__name__ == 'Foo'
except:
return False
print(isFoo(Foo(...)))
print(isFoo(42))
isFoo
函数测试传递的 obj
是否是在名为 big_file
的模块中定义的一些名为 Foo
的 class 的实例。如果您有多个具有相同名称的模块,这原则上可能会失败,例如在不同的包中,当然这对你来说很可能不是问题。
编辑,处理 Foo
的子classes正如 sytech 所指出的,上述解决方案在 subclasses 上失败。也就是说,isFoo(obj)
returns False
如果 obj
是 Foo
的子 class 的实例,而 isinstance(obj, Foo)
returns True
。下面的代码是上面的通用版本,解决了这个问题:
import inspect
def isFoo(obj):
for cls in inspect.getmro(type(obj)):
try:
if cls.__module__ == 'big_file' and cls.__name__ == 'Foo':
return True
except:
pass
return False
这使用与以前相同的测试,但现在不仅针对 obj
的 class
,而且还针对其所有超级classes。
编辑,使想法失败证明
上面的唯一警告是我们只测试模块 name 而不是绝对路径。如前所述,只有当您的项目包含多个具有相同名称的模块时才会出现问题,其中包含相同名称的 classes。我们 可以 但是测试路径,当然这需要您在代码中指定模块的绝对路径:
import inspect
def my_isinstance(obj, classinfo):
if isinstance(classinfo[0], str):
classinfo = (classinfo, )
for module_path, cls_name in classinfo:
for cls in inspect.getmro(type(obj)):
try:
if inspect.getmodule(cls).__file__ == module_path and cls.__name__ == cls_name:
return True
except:
pass
return False
print(my_isinstance(Foo(1, 2), ('/path/to/big_file.py', 'Foo')))
print(my_isinstance(42, ('/path/to/big_file.py', 'Foo')))
为了使函数完全类似于内置 isinstance
,它现在还支持多个 class 作为输入(例如 tuple
形式 (('/path/to/module1.py', 'Foo'), ('/path/to/module2.py', 'Bar'))
,用于检查 obj
是否是 Foo
或 Bar
).
虽然这个版本是防弹的,但我个人更喜欢以前的isFoo
,因为指定模块的绝对路径有点难看。