ctypes与_ctypes - 后者为什么存在?

12

我最近了解到Python不仅有一个名为ctypes的模块,它有一个文档页面,还有一个名为_ctypes的模块,它是没有文档但在文档中提到几次。一些互联网上的代码,例如这个Stack Overflow答案中的片段,使用这个神秘的未记录的_ctypes模块。

一些实验表明,这两个模块具有类似但非完全相同的docstrings和重叠但非相同的属性列表:

Python 3.7.4 (default, Sep  7 2019, 18:27:02) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes, _ctypes
>>> print(ctypes.__doc__)
create and manipulate C data types in Python
>>> print(_ctypes.__doc__)
Create and manipulate C compatible data types in Python.
>>> dir(ctypes)
['ARRAY', 'ArgumentError', 'Array', 'BigEndianStructure', 'CDLL', 'CFUNCTYPE', 'DEFAULT_MODE', 'LibraryLoader', 'LittleEndianStructure', 'POINTER', 'PYFUNCTYPE', 'PyDLL', 'RTLD_GLOBAL', 'RTLD_LOCAL', 'SetPointerType', 'Structure', 'Union', '_CFuncPtr', '_FUNCFLAG_CDECL', '_FUNCFLAG_PYTHONAPI', '_FUNCFLAG_USE_ERRNO', '_FUNCFLAG_USE_LASTERROR', '_Pointer', '_SimpleCData', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_c_functype_cache', '_calcsize', '_cast', '_cast_addr', '_check_size', '_ctypes_version', '_dlopen', '_endian', '_memmove_addr', '_memset_addr', '_os', '_pointer_type_cache', '_reset_cache', '_string_at', '_string_at_addr', '_sys', '_wstring_at', '_wstring_at_addr', 'addressof', 'alignment', 'byref', 'c_bool', 'c_buffer', 'c_byte', 'c_char', 'c_char_p', 'c_double', 'c_float', 'c_int', 'c_int16', 'c_int32', 'c_int64', 'c_int8', 'c_long', 'c_longdouble', 'c_longlong', 'c_short', 'c_size_t', 'c_ssize_t', 'c_ubyte', 'c_uint', 'c_uint16', 'c_uint32', 'c_uint64', 'c_uint8', 'c_ulong', 'c_ulonglong', 'c_ushort', 'c_void_p', 'c_voidp', 'c_wchar', 'c_wchar_p', 'cast', 'cdll', 'create_string_buffer', 'create_unicode_buffer', 'get_errno', 'memmove', 'memset', 'pointer', 'py_object', 'pydll', 'pythonapi', 'resize', 'set_errno', 'sizeof', 'string_at', 'wstring_at']
>>> dir(_ctypes)
['ArgumentError', 'Array', 'CFuncPtr', 'FUNCFLAG_CDECL', 'FUNCFLAG_PYTHONAPI', 'FUNCFLAG_USE_ERRNO', 'FUNCFLAG_USE_LASTERROR', 'POINTER', 'PyObj_FromPtr', 'Py_DECREF', 'Py_INCREF', 'RTLD_GLOBAL', 'RTLD_LOCAL', 'Structure', 'Union', '_Pointer', '_SimpleCData', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__version__', '_cast_addr', '_memmove_addr', '_memset_addr', '_pointer_type_cache', '_string_at_addr', '_unpickle', '_wstring_at_addr', 'addressof', 'alignment', 'buffer_info', 'byref', 'call_cdeclfunction', 'call_function', 'dlclose', 'dlopen', 'dlsym', 'get_errno', 'pointer', 'resize', 'set_errno', 'sizeof']

我瞬间认为我看到的可能是一个"加速器模块", 但我认为不可能是这样,因为ctypes的当前实现无条件地从_ctypes导入内容。并不清楚_ctypes是否只是实现细节;它至少公开了一个公共成员PyObj_FromPtr, 这对于编写Python代码很有用, 在CPython源代码中没有使用 - 这可能表明我们在编写Python代码时可以导入和使用它?
为什么Python有这两个基本相同名称的模块?两者之间的责任分工是什么,何时应该使用其中一个?我应该将_ctypes视为标准库的一部分,还是作为不应该触及的实现细节?

我最初是在Tiran的评论中第一次听说这个。它的源代码在py 3.8中,位于.../Modules/_ctypes/callproc.c。按照惯例,_ctypes前缀表示它是用C编写的代码,并且是cpython解释器的实现细节。使用时需自行承担风险——尽管我怀疑它会在此时消失或更改其接口。 - martineau
相关 https://dev59.com/rFMI5IYBdhLWcg3w2_ZT - snakecharmerb
1个回答

10

_ctypes 的存在是因为很多 ctypes 必须用 C 写。 ctypes_ctypes 都存在的原因是并非所有的 ctypes 都必须用 C 写。 ctypes 包含了在 Python 中更方便编写的部分。

_ctypes 中有一些没有前导下划线的内容,这些内容并未被 ctypes 公开,这并不意味着这些内容可以被任何东西使用。有时候程序就是会留下这样的东西,可能在开发的某个阶段曾经有意将其公开。

_ctypes 是一个实现细节。请使用 ctypes


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接