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。