【问题标题】:In python at runtime determine if an object is a class (old and new type) instance在运行时在python中确定对象是否是类(旧类型和新类型)实例
【发布时间】:2012-08-27 07:40:33
【问题描述】:

我正在尝试使用 h5py 模块将一组深度嵌套的类、属性、绑定方法等写入 HDF5 文件以进行长期存储。我真的很亲近。我似乎无法解决的唯一问题是在运行时以编程方式找出一种方法来确定某物是否是类实例类型,而不是列表、int 等。我需要递归到类实例,但显然不应该递归到 int、float 等。这需要对旧式和新式类都有效。我研究的东西不起作用/我无法开始工作:

使用检查模块

>>> class R(object): pass
...
>>> _R = R()
>>> import inspect
>>> inspect.isclass(_R)
False
>>> inspect.isclass(R)
True

这没什么用,我需要像 inspect.isclassinstance(_R) 这样的函数来返回 True

使用类型模块

如果您使用旧式类,则有一个名为 InstanceType 的类型可以匹配旧式类的实例,如下面的代码所示

>>> import types
>>> class R(): pass #old-style class
...
>>> _R = R()
>>> import types
>>> type(_R) is types.InstanceType
True
>>> class R(object): pass #new-style class
...
>>> _R = R()
>>> type(_R) is types.InstanceType
False

但是如果你使用新式类,types中没有对应的类型

【问题讨论】:

  • 什么定义了“类实例”? (考虑一下:isinstance(5, int) and type(int) is type(R))您究竟需要检测什么?我感觉你的序列化方法有缺陷。
  • @delnan 在我上面的例子中,我需要匹配新旧样式的类实例。我不确定我是否遵循您的考虑...评论。想把它拼出来吗?
  • 您似乎从类实例中排除的对象几乎没有什么特别之处。 int 和其他内置类型是(几乎!)所有意图和目的的新型类,而像 5 这样的值是(几乎?)所有意图和目的,这些类的实例。虽然这些类型,像所有其他类型一样,需要对序列化进行特殊处理,但“是一个对象”是一个无用的区分。你区分它们的论点对我来说似乎很可疑。
  • 啊,我想你是对的,现在一切都是“类”。甚至5 也是一个类实例。嗯...回到绘图板。

标签: python class


【解决方案1】:

虽然发布者很可能需要重新考虑他的设计,但在某些情况下,有必要区分用 C 创建的内置/扩展类型的实例和用 @987654324 用 Python 创建的类的实例@ 陈述。虽然两者都是类型,但后者是 CPython 内部称为“堆类型”的一类类型,因为它们的类型结构是在运行时分配的。可以在__repr__ 输出中看到 python 继续区分它们:

>>> int       # "type"
<type 'int'>
>>> class X(object): pass
... 
>>> X         # "class"
<class '__main__.X'>

__repr__ 的区别正是通过检查类型是否为堆类型来实现的。

根据应用程序的具体需求,is_class_instance 函数可以通过以下方式之一实现:

# Built-in types such as int or object do not have __dict__ by
# default. __dict__ is normally obtained by inheriting from a
# dictless type using the class statement.  Checking for the
# existence of __dict__ is an indication of a class instance.
#
# Caveat: a built-in or extension type can still request instance
# dicts using tp_dictoffset, and a class can suppress it with
# __slots__.
def is_class_instance(o):
    return hasattr(o, '__dict__')

# A reliable approach, but one that is also more dependent
# on the CPython implementation.
Py_TPFLAGS_HEAPTYPE = (1<<9)       # Include/object.h
def is_class_instance(o):
    return bool(type(o).__flags__ & Py_TPFLAGS_HEAPTYPE)

编辑

这里是函数的第二个版本的解释。它使用 CPython 内部用于其自身目的的相同测试来真正测试该类型是否为“堆类型”。这确保了对于堆类型(“类”)的实例,它总是返回 True,对于非堆类型(“类型”,还有旧式类,这很容易修复)的实例,它总是返回 False。它通过检查 C 级 PyTypeObject structuretp_flags member 是否设置了 Py_TPFLAGS_HEAPTYPE 位来做到这一点。该实现的弱点在于它将Py_TPFLAGS_HEAPTYPE 常量的值硬编码为当前观察到的值。 (这是必要的,因为该常量没有通过符号名称暴露给 Python。)虽然理论上这个常量可以改变,但在实践中极不可能发生,因为这样的改变会无缘无故地破坏现有扩展模块的 ABI。查看Include/object.h 中的definitions of Py_TPFLAGS constants,很明显新的被小心地添加而不会干扰旧的。另一个弱点是此代码在非 CPython 实现(例如 Jython 或 IronPython)上运行的机会为零。

【讨论】:

  • 太棒了!我将用一个小模块添加另一个答案,以便它为旧式和新式类返回 True。这是一种享受。你能解释一下你在第二个解决方案中做了什么吗?我不跟。它虽然有效,但很好
  • 我现在添加了一个解释;希望对您有所帮助。
  • 现象级。 遗憾的是,这并不像 Python 3.x 下所宣传的那样有效:L 已死,因此幻数分配应为 Py_TPFLAGS_HEAPTYPE = (1&lt;&lt;9)。同样,如前所述,这在使用 __slots__ 优化的纯 Python 类上失败。但是对于 CPython 特定的“堆类型”的非常精心编写的解释是一个有利可图的金矿。我实际上从未见过一种可靠的方法来区分纯 Python 和基于 C 的类,即使它 CPython 特定的。每一个赞成票都是应得的。
  • @CecilCurry 谢谢。请注意,据我所知,只有 first 版本的函数对于定义 __slots__ 而没有 dict 的 Python 类会失败。我现在删除了L(这在 Python 2 中也不是必需的),并且代码在 Python 2 和 3 中都应该可以正常工作。
【解决方案2】:

感谢@user4815162342,我已经能够让它工作。这是一个稍作修改的版本,它将为旧式和新式类的实例返回 True:

#Added the check for old-style class
Py_TPFLAGS_HEAPTYPE = (1L<<9)       # Include/object.h
def is_class_instance(o):
    import types
    return (bool(type(o).__flags__ & Py_TPFLAGS_HEAPTYPE) 
            or type(o) is types.InstanceType)

【讨论】:

    【解决方案3】:

    tl;dr 只需调用定义在远、远下方的is_object_pure_python() 函数即可。

    ibell一样,user4815162342的权威Python 2.x-specific solution给我留下了深刻的印象。然而,在 Pythonic 的天堂里,一切都不是很好。

    问题。问题无处不在。

    该解决方案(尽管很有见地)遇到了一点bit rot 无法通过简单的编辑可以轻松解决,包括:

    • L 类型后缀在 Python 3.x 下不受支持。 诚然,很容易解决。
    • 交叉解释器 is_class_instance() 实现无法考虑使用 __slots__ 优化的纯 Python 类。
    • 特定于 CPython 的 is_class_instance() 实现在非 CPython 解释器(例如 pypy)下失败。
    • 没有可比较的实现来检测 (而不是 类实例)是纯 Python 还是基于 C。

    解决方案!无处不在的解决方案!

    为了解决这些问题,以下特定于 Python 3.x 的解决方案删除了​​ L,检测到 __slots__,已被重构,以便在 CPython 下选择更可靠的 CPython 特定 is_class_instance() 实现并回退到在所有其他解释器下的不太可靠的交叉解释器 is_class_instance() 实现,已被推广到检测类和类实例。

    为了理智,让我们先检测类实例:

    import platform
    
    # If the active Python interpreter is the official CPython implementation,
    # prefer a more reliable CPython-specific solution guaranteed to succeed.
    if platform.python_implementation() == 'CPython':
        # Magic number defined by the Python codebase at "Include/object.h".
        Py_TPFLAGS_HEAPTYPE = (1<<9)
    
        def is_instance_pure_python(obj: object) -> bool:
            '''
            `True` if the passed object is an instance of a pure-Python class _or_
            `False` if this object is an instance of a C-based class (either builtin
            or defined by a C extension).
            '''
    
            return bool(type(obj).__flags__ & Py_TPFLAGS_HEAPTYPE)
    
    # Else, fallback to a CPython-agnostic solution typically but *NOT*
    # necessarily succeeding. For all real-world objects of interest, this is
    # effectively successful. Edge cases exist but are suitably rare.
    else:
        def is_instance_pure_python(obj: object) -> bool:
            '''
            `True` if the passed object is an instance of a pure-Python class _or_
            `False` if this object is an instance of a C-based class (either builtin
            or defined by a C extension).
            '''
    
            return hasattr(obj, '__dict__') or hasattr(obj, '__slots__')
    

    证据在 Guido 的布丁中

    单元测试证明了令人不安的事实:

    >>> class PurePythonWithDict(object): pass
    >>> class PurePythonWithSlots(object): __slots__ = ()
    >>> unslotted = PurePythonWithDict()
    >>> slotted = PurePythonWithSlots()
    >>> is_instance_pure_python(unslotted)
    True
    >>> is_instance_pure_python(slotted)
    True
    >>> is_instance_pure_python(3)
    False
    >>> is_instance_pure_python([3, 1, 4, 1, 5])
    False
    >>> import numpy
    >>> is_instance_pure_python(numpy.array((3, 1, 4, 1, 5)))
    False
    

    这是否可以推广到没有实例的类?

    是的,但这样做并非易事。检测一个 class(而不是 class instance)是纯 Python 还是基于 C 的异常困难。为什么?因为即使是基于 C 的类也提供了__dict__ 属性。因此,hasattr(int, '__dict__') == True

    尽管如此,这是一种老套的方式,但有一个老套的意志。由于未知(可能是陈词滥调)原因,dir() 内置函数从其返回的列表中删除了 __dict__ 属性名称仅适用于基于 C 的类。因此,检测一个类是纯 Python 或基于 C 的,以交叉解释器的方式减少到迭代搜索 dir() 返回的列表以查找 __dict__。为了胜利:

    import platform
    
    # If the active Python interpreter is the official CPython interpreter,
    # prefer a more reliable CPython-specific solution guaranteed to succeed.
    if platform.python_implementation() == 'CPython':
        # Magic number defined by the Python codebase at "Include/object.h".
        Py_TPFLAGS_HEAPTYPE = (1<<9)
    
        def is_class_pure_python(cls: type) -> bool:
            '''
            `True` if the passed class is pure-Python _or_ `False` if this class
            is C-based (either builtin or defined by a C extension).
            '''
    
            return bool(cls.__flags__ & Py_TPFLAGS_HEAPTYPE)
    
    # Else, fallback to a CPython-agnostic solution typically but *NOT*
    # necessarily succeeding. For all real-world objects of interest, this is
    # effectively successful. Edge cases exist but are suitably rare.
    else:
        def is_class_pure_python(cls: type) -> bool:
            '''
            `True` if the passed class is pure-Python _or_ `False` if this class
            is C-based (either builtin or defined by a C extension).
            '''
    
            return '__dict__' in dir(cls) or hasattr(cls, '__slots__')
    

    更多证明。更多布丁。

    更多测试驱动的真实性:

    >>> class PurePythonWithDict(object): pass
    >>> class PurePythonWithSlots(object): __slots__ = ()
    >>> is_class_pure_python(PurePythonWithDict)
    True
    >>> is_class_pure_python(PurePythonWithSlots)
    True
    >>> is_class_pure_python(int)
    False
    >>> is_class_pure_python(list)
    False
    >>> import numpy
    >>> is_class_pure_python(numpy.ndarray)
    False
    

    这就是她写的全部

    为了通用性,让我们将上面定义的低级函数统一为两个高级函数,支持所有可能的 Python 解释器下的所有可能类型:

    def is_object_pure_python(obj: object) -> bool:
       '''
       `True` if the passed object is either a pure-Python class or instance of
       such a class _or_ `False` if this object is either a C-based class
       (builtin or defined by a C extension) or instance of such a class.
       '''
    
       if isinstance(obj, type):
           return is_class_pure_python(obj)
       else:
           return is_instance_pure_python(obj)
    
    
    def is_object_c_based(obj: object) -> bool:
       '''
       `True` if the passed object is either a C-based class (builtin or
       defined by a C extension) or instance of such a class _or_ `False` if this
       object is either a pure-Python class or instance of such a class.
       '''
    
       return not is_object_pure_python(obj)
    

    看哪!纯 Python。

    【讨论】:

      猜你喜欢
      • 2011-04-19
      • 2011-07-31
      • 2011-08-23
      • 2022-07-23
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 2010-11-22
      相关资源
      最近更新 更多