5

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

4

3 回答 3

6

虽然发帖人很可能需要重新考虑他的设计,但在某些情况下,有必要区分用 C 语言创建的内置/扩展类型的实例和用该class语句在 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 级结构的tp_flags成员是否设置了位来做到这一点。实现的弱点在于它硬编码了PyTypeObjectPy_TPFLAGS_HEAPTYPEPy_TPFLAGS_HEAPTYPE恒定为当前观察到的值。(这是必要的,因为该常量没有通过符号名称暴露给 Python。)虽然理论上这个常量可以改变,但在实践中极不可能发生,因为这样的改变会无缘无故地破坏现有扩展模块的 ABI。查看中常量定义Py_TPFLAGSInclude/object.h,很明显,新的常量正在被小心地添加,而不会干扰旧的常量。另一个弱点是此代码在非 CPython 实现(例如 Jython 或 IronPython)上运行的机会为零。

于 2012-09-02T15:31:07.417 回答
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)
于 2012-09-03T20:58:39.763 回答
0

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

ibell一样,user4815162342的权威Python 2.x 专用解决方案给我留下了深刻的印象。然而,在 Pythonic 的天堂里,一切都不是很好。

问题。问题无处不在。

该解决方案(尽管很有见地)遭受了一些简单的编辑 无法轻松解决的问题,包括:

  • LPython 3.x 不支持类型后缀。诚然,微不足道的解决方法。
  • 交叉解释器is_class_instance()实现无法解释使用__slots__.
  • CPython 特定的is_class_instance()实现在非 CPython 解释器(例如,pypy)下失败。
  • 没有可比的实现来检测(而不是类实例)是纯 Python 还是基于 C 的。

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

为了解决这些问题,以下特定于 Python 3.x 的解决方案已经过重构,以便在 CPython 下选择更可靠的 CPython 特定L实现,并回退到所有其他解释器下不太可靠的跨解释器实现,以及已被推广到检测类和类实例。__slots__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__')

证据在圭多的布丁里

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

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

尽管如此,如果这是一种骇人听闻的方式,就会有一种骇人听闻的意志。由于未知(可能是平庸)的原因,dir()内置函数__dict__从其返回的列表中删除属性名称,仅用于基于 C 的类。因此,以交叉解释器的方式检测一个类是纯 Python 还是基于 C 的,减少了迭代搜索 for 返回的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。

于 2016-12-07T08:42:42.583 回答