确定对象是否为Foo类型而不导入类型Foo

时间:2018-03-30 15:42:33

标签: python python-2.6 typechecking

我们说我在文件中定义了一个类:

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

2 个答案:

答案 0 :(得分:5)

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

但是,如果您的其他文件在某些​​情况下只会遇到big_file.Foobig_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

答案 1 :(得分:2)

如果出于任何非标准原因导致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是否是名为Foo的模块中定义的名为big_file的某个类的实例。如果你有多个具有相同名称的模块,原则上这可能会失败,例如在不同的包装中,但当然这对您来说很可能不是问题。

编辑,负责Foo

的子类

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

这使用与以前相同的测试,但现在不仅仅是class的{​​{1}},还包括所有超类。

编辑,使想法失败证明

上述唯一的警告是我们只测试模块 name 而不是绝对路径。如前所述,如果您的项目包含多个具有相同名称的模块(包含具有相同名称的类),则这只是一个问题。我们可以测试路径,但当然这要求您在代码中指定模块的绝对路径:

obj

为了使函数完全类似于内置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,用于检查是否{{1} }是(('/path/to/module1.py', 'Foo'), ('/path/to/module2.py', 'Bar'))obj)的实例。

虽然这个版本是防弹的,但我个人更喜欢上一个Foo,因为指定模块的绝对路径有点难看。