在运行时的python中确定对象是否是类(旧类型和新类型)实例

时间:2012-09-02 02:59:33

标签: python class

我正在尝试使用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

中没有相应的类型

3 个答案:

答案 0 :(得分:5)

虽然海报很可能需要重新考虑他的设计,但在某些情况下,合法需要区分用C创建的内置/扩展类型的实例和用{{{ 1}}陈述。虽然两者都是类型,但后者是CPython内部称为“堆类型”的类型,因为它们的类型结构是在运行时分配的。那个python继续区分它们可以在class输出中看到:

__repr__

>>> int # "type" <type 'int'> >>> class X(object): pass ... >>> X # "class" <class '__main__.X'> 区别是通过检查类型是否为堆类型来实现的。

根据应用程序的确切需要,可以通过以下方式之一实现__repr__功能:

is_class_instance

修改

以下是该功能的第二个版本的说明。它确实使用CPython内部用于其自身目的的相同测试来测试该类型是否为“堆类型”。这确保了对于堆类型(“类”)的实例它将始终返回True,对于非堆类型的实例(“类型”,以及旧样式类,这很容易修复)将始终返回False。它通过检查C级tp_flags memberPyTypeObject structure是否设置了# 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) 位来实现。实现的薄弱部分是它将Py_TPFLAGS_HEAPTYPE常量的值硬编码为当前观察到的值。 (这是必要的,因为常量不会通过符号名称暴露给Python。)虽然理论上这个常量可能会改变,但实际上很可能不会发生这种情况,因为这样的改变会无意中破坏现有扩展模块的ABI。查看Py_TPFLAGS_HEAPTYPE中的definitions of Py_TPFLAGS constants,显然会在不打扰旧版的情况下仔细添加新版本。另一个缺点是,这个代码在非CPython实现上运行的可能性为零,例如Jython或IronPython。

答案 1 :(得分:0)

感谢@ 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)

答案 2 :(得分:0)

tl; dr 只需调用远远低于此定义的is_object_pure_python()函数。

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

问题。问题无处不在。

该解决方案(虽然富有洞察力)遭受了一些bit rot 而不是通过简单的编辑轻易解决,包括:

  • Python 3.x下不支持L类型后缀。 不可否认,可以轻易解决。
  • 交叉解释器is_class_instance()实现无法解释使用__slots__优化的纯Python类。
  • CPython特定的is_class_instance()实现在非CPython解释器(例如,pypy)下失败。
  • 没有类似的实现来检测(而不是类实例)是纯Python还是基于C语言。

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

要解决这些问题,以下特定于Python 3.x的解决方案将丢弃L,检测__slots__,已经过重构,以便更喜欢更可靠的CPython特定is_class_instance()实现在CPython下,回退到所有其他解释器下不太可靠的交叉解释器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&Pudding的布丁

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

>>> 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

这是否通用于没有实例的类

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

尽管如此,如果这是一种hacky方式,那就是一种hacky意志。对于未知(可能是平庸的)原因,dir() builtin仅从基于C的类中返回列表中删除__dict__属性名称。因此,以交叉解释方式检测类是纯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

所有她写的

一般来说,让我们将上面定义的低级函数统一为两个支持所有可能类型的高级函数:

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。