确定对象是否属于 Foo 类型,而不导入类型 Foo



假设我在文件中定义了一个类:

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基的字符串表示吗?还是有另一种更规范的方式?

通常,这不是问题。如果您有big_file.Foo的实例,则即使未从其他文件显式引用,其父模块也已在较早之前导入。Python 模块在第一次导入时只加载一次(前提是您没有执行任何显式重新加载或弄乱sys.modules)。由于它已被导入,因此在其他文件中执行import big_file应该会立即运行。

但是,如果您的其他文件在某些情况下只会遇到big_file.Foo,并且big_file仅在实际需要时才导入到其他地方,那么您可以检查对象的类(这不支持子类):

def do_stuff(obj):
if (obj.__class__.__module__, obj.__class__.__name__) == ('big_file', 'Foo'):
do_stuff
else:
do_other_stuff

由于您已经指示可以在应用程序中的任何位置导入big_file.Foo并且您希望支持子类,因此您可以检查其模块是否已导入并有条件地检查类型。

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由于任何非标准原因确实需要很长时间才能导入,您确实可以使用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的某个类的实例。如果您有多个具有相同名称的模块,例如在不同的包中,这原则上可能会失败,但当然这对您来说很可能不是问题。

编辑,照顾Foo的子类

正如sytech所指出的,上述解决方案在子类上失败。也就是说,如果objFoo子类的实例,则isFoo(obj)返回False,而isinstance(obj, Foo)返回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

这使用了与以前相同的测试,但现在不仅针对objclass,还针对其所有超类。

编辑,使想法失败证明

上面唯一需要注意的是,我们只测试模块名称而不是绝对路径。如前所述,仅当您的项目包含多个同名模块(包含同名类)时才会出现问题。但是,我们可以测试路径,当然这需要您在代码中指定模块的绝对路径:

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,它现在还支持多个类作为输入(例如tuple的表单(('/path/to/module1.py', 'Foo'), ('/path/to/module2.py', 'Bar')),用于检查objFoo还是Bar的实例)。

虽然这个版本是防弹的,但我个人更喜欢以前的isFoo,因为指定模块的绝对路径有点丑陋。

相关内容

最新更新