【问题标题】:Determine if object is of type Foo without importing type Foo在不导入 Foo 类型的情况下确定对象是否为 Foo 类型
【发布时间】:2018-03-30 15:42:33
【问题描述】:

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

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 基的字符串表示?还是有其他更规范的方法?

【问题讨论】:

  • obj.__class__.__name__ == 'Foo',但我永远不会这样做。你真的需要检查类型,而不是使用鸭子打字吗?
  • 您可以先在sys.modules 中查看big_file 是否已导入。如果没有,那么您的对象不是Foo。如果有,您可以非常便宜地在现场导入它,然后在isinstance 那里检查。
  • 如果你有一个Foo 的实例,是否存在Foo 定义的模块不会sys.modules 中的情况? (故意修改sys.modules除外)
  • @mgilson 我想你正在做一些事情,你要写一个完整的答案吗?

标签: python python-2.6 typechecking


【解决方案1】:

通常,这不是问题。如果您有一个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

【讨论】:

  • 如果Foo 类型的对象已被dillpickle 之类的序列化程序恢复,情况会怎样? big_file 可能无法导入
  • @OlegRybalchenko 我对使用pickle 的经验很少,但我认为它必须查找实际的类并导入它们,然后使用__getstate____setstate__
  • 但是如果obj不是Foo,那么Foo就不会被导入,我也不想花时间导入它。但就像其他人建议的那样,我可以检查 sys.modules 以查看它是否已导入
  • @C_Z_,如果你的情况足够的话,这看起来是一个好方法。作为替代方案,如果您仍需要查找在模块顶层定义的对象,请查看标准库中的pyclbr
  • 你不能检查一个对象的 MRO 吗?例如inspect.getmro(type(obj)) 你可以检查 MRO 中的所有类名......虽然我不认为这是特别推荐的。
【解决方案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 是否是在名为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 上,而且在其所有超类上。

编辑,使想法失败

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

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,它现在还支持多个类作为输入(例如(('/path/to/module1.py', 'Foo'), ('/path/to/module2.py', 'Bar')) 形式的tuples,用于检查obj 是否是@987654343 的一个实例@ 或 Bar)。

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

【讨论】:

  • 如果您有一个对象是Foo 的子类的实例,则可能会遇到这样的问题。 isinstance 适用于子类。这个实现没有,所以行为不太一样。
  • @cpburnz 谢谢,我已经更新了答案。 isinstance(obj, Foo) 是否保证返回 True 只是因为 type(obj)Foo 的调用树(方法解析顺序)中?您可以为此提供参考吗?请注意,在 Python 中,不是 type(obj) 的直接祖先的类可以出现在调用树中。
  • @cpburnz 太好了。反正我不喜欢tuple 连接。
  • @jmd_dk 是的,如果类在调用树中的任何位置,isinstance() 保证返回 True。见isinstance()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-19
  • 2016-11-13
  • 2017-07-28
相关资源
最近更新 更多