Python的C语言接口 - 详解官方文档

Python的C语言接口 - 详解官方文档


本文将介绍Python/C API。本文将给出Python的官方英文文档及其翻译,和此对应用法的说明。翻译可能不完全按照原文翻译。

介绍 / Introduce

官方对Python/C API的介绍:

The Application Programmer’s Interface to Python gives C and C++ programmers access to the Python interpreter at a variety of levels. The API is equally usable from C++, but for brevity it is generally referred to as the Python/C API. There are two fundamentally different reasons for using the Python/C API. The first reason is to write extension modules for specific purposes; these are C modules that extend the Python interpreter. This is probably the most common use. The second reason is to use Python as a component in a larger application; this technique is generally referred to as embedding Python in an application.
Writing an extension module is a relatively well-understood process, where a “cookbook” approach works well. There are several tools that automate the process to some extent. While people have embedded Python in other applications since its early existence, the process of embedding Python is less straightforward than writing an extension.
Many API functions are useful independent of whether you’re embedding or extending Python; moreover, most applications that embed Python will need to provide a custom extension as well, so it’s probably a good idea to become familiar with writing an extension before attempting to embed Python in a real application.[1] (Introduction – Python 3.7.12 documentation, Python Software Foundation).

译:Python应用程序编程接口使得C/C++程序员可以在各种级别上使用Python解释器。这些API对于C++来说同样实用。但是为了简洁,它们被像Python的C语言应用程序接口(以下简称Python/C API)一样提供。从根本上讲,有2种不同的使用Python/C API的原因。首先是为了一些特殊用途用C语言编写Python的扩展模块;这些由C写成的模块可以扩展Python解释器。这可能是Python/C API最常见的用途。其次,可以使用Python作为一个大型应用程序的组成部分;这个技巧通常指将Python嵌入一个应用程序。
编写扩展模块这个用途相当好理解,这种方法非常实用。有数种工具可以自动完成这个扩展操作。当人们试图将Python运用进一个已存在的应用程序时,将Python嵌入应用将会比编写扩展更加直截了当。
许多有用的接口函数不论被用于扩展或是嵌入都能独立工作;此外,大多数嵌入Python的应用程序也需要提供自定义扩展,因此在试图向一个实际的应用程序中嵌入Python前,先编写一个较为熟悉使用的扩展或许是一个好方法。

代码标准 / Coding Standards

If you’re writing C code for inclusion in CPython, you must follow the guidelines and standards defined in PEP 7. These guidelines apply regardless of the version of Python you are contributing to. Following these conventions is not necessary for your own third party extension modules, unless you eventually expect to contribute them to Python.

译:如果您正准备将C代码包含进CPython项目中,您须遵守以下由PEP 7规范定义的指导方针和标准。这些指导方针适用于任何版本的Python。遵循这些约定对于您自己的第三方扩展模块来说是不必要的,除非您最终希望将它们贡献给Python。

PEP 7(Python Enhancement Proposal),直译为“Python(代码)改善建议”,是对Python相关代码的编写规范。Python的C语言扩展/内嵌,应当遵循PEP 7规范中的PEP 7 – Style Guide for C Code [2]。

关于Style Guide for C Code的更多信息,详见https://www.python.org/dev/peps/pep-0007/

包含文件 / Include Files

All function, type and macro definitions needed to use the Python/C API are included in your code by the following line:

译:使用Python/C API的所有函数、类型和宏定义都应被您的代码使用下述代码包含:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

CPython的所有接口都在Python.h中被定义/包含。另外,Python官方文档建议定义宏PY_SSIZE_T_CLEAN

This implies inclusion of the following standard headers: <stdio.h>, <string.h>, <errno.h>, <limits.h>, <assert.h> and <stdlib.h> (if available).

译:这意味着将包含以下标准头文件:<stdio.h><string.h><errno.h><limits.h><assert.h><stdlib.h>(如果可用的话)。

<stdio.h>是标准输入输出头文件(stdio是standard input/output的缩写),<string.h>是C语言的字符串(string)处理的头文件,<errno.h>是用于获取错误信息[3]的头文件,<limits.h>是关于整数的头文件[4],<assert.h>是关于断言(assert)的头文件,<stdlib.h>是标准库(standard libraries)的头文件。

Note: Since Python may define some pre-processor definitions which affect the standard headers on some systems, you must include Python.h before any standard headers are included.
It is recommended to always define PY_SSIZE_T_CLEAN before including Python.h. See Parsing arguments and building values for a description of this macro.
All user visible names defined by Python.h (except those defined by the included standard headers) have one of the prefixes Py or _Py. Names beginning with _Py are for internal use by the Python implementation and should not be used by extension writers. Structure member names do not have a reserved prefix.
Note: User code should never define names that begin with Py or _Py. This confuses the reader, and jeopardizes the portability of the user code to future Python versions, which may define additional names beginning with one of these prefixes.
The header files are typically installed with Python. On Unix, these are located in the directories prefix/include/pythonversion/ and exec_prefix/include/pythonversion/, where prefix and exec_prefix are defined by the corresponding parameters to Python’s configure script and version is '%d.%d' % sys.version_info[:2]. On Windows, the headers are installed in prefix/include, where prefix is the installation directory specified to the installer.
To include the headers, place both directories (if different) on your compiler’s search path for includes. Do not place the parent directories on the search path and then use #include <pythonX.Y/Python.h>; this will break on multi-platform builds since the platform independent headers under prefix include the platform specific headers from exec_prefix.
C++ users should note that although the API is defined entirely using C, the header files properly declare the entry points to be extern "C". As a result, there is no need to do anything special to use the API from C++.

译::自从Python可能通过定义一些预处理器定义来确定对应系统上使用的标准头文件时,您须包含Python.h在任何标准头文件前。
推荐您在包含Python.h前定义宏PY_SSIZE_T_CLEAN。更多关于此宏定义的描述,请见解析参数和构建值一节。
所有由Python.h定义的用户可见的标识名(不包括由所包含的标准头文件定义的标识名)都拥有一个Py或_Py前缀。由_Py开头的标识用于内部Python实现,编写扩展时不应使用它们。结构体成员名不拥有这类保留标识符。
:用户代码不应定义由Py或_Py开头的标识名。这可能使您的代码的阅读者混淆,并降低您的代码在更高版本Python中的可读性,因为未来Python可能会定义新的有这类前缀的标识名。
头文件通常与Python一起安装。在(类)Unix上,它们位于prefix/include/pythonversion/exec_prefix/include/pythonversion/目录中,其中prefixexec_prefix由Python的配置脚本的相应参数定义,版本为'%d.%d' % sys.version_info[:2]。在Windows上,头文件安装在prefix/include中,其中prefix是为安装程序指定的安装目录。
要包含这些头文件,请将两个目录(如果不同)放在编译器的包含搜索路径上。不要将父目录放在搜索路径上,然后使用#include<pythonX.Y/Python.h>;这将在多平台构建中出现异常,因为prefix下的与平台无关的头文件包括来自exec_prefix的平台特定的头文件。
C++用户应该注意,虽然API完全用C定义,但是头文件正确地声明入口点是extern "C"。因此,不需要做任何特殊的事情来使用来自C++的API。

例如在Windows系统上,假如您将Python 3.7.4(32位)安装在C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\目录下,那么请将目录C:\Users\Administrator\AppData\Local\Programs\Python\Python37-32\include\加入编译器的头文件搜索目录。例如在VS 2010上,点击菜单栏>项目>项目 XXX 属性( P )打开项目属性对话框,在编译器分栏中找到相应的头文件目录,将上述目录添加。然后单击确定应用
另外,实际经验得出,为了简便、有效地使用Python/C API,您应当将prefix/libs加入附加库目录。它位于项目属性对话框中的链接器一栏中。然后在链接器一栏的输入分支中将python.lib加入库列表。

实用宏 / Useful Macros

Several useful macros are defined in the Python header files. Many are defined closer to where they are useful (e.g. Py_RETURN_NONE). Others of a more general utility are defined here. This is not necessarily a complete listing.

译:有大量实用的宏在Python头文件中被定义。许多实用宏的定义与它们的使用场景相近(例如Py_RETURN_NONE)。这里给出其它更通用的实用程序(宏)定义,它们包括但不限于以下定义。

I. Py_UNREACHABLE()

Use this when you have a code path that you do not expect to be reached. For example, in the default: clause in a ``switch statement for which all possible values are covered in case statements. Use this in places where you might be tempted to put an assert(0) or abort() call.
New in version 3.7.

译:当你不希望程序运行到某代码位置时使用此宏。例如,在一个switch语句的default:中使用它,因为所有可能出现的值(正常情况下)都应被包含入case语句中。在您可能想调用assert(0)abort()时使用此宏。
此宏在3.7版本中新增。

此宏主要用于处理代码运行至异常位置时的情况。例如,在一个C扩展函数func(a)中,您可能会希望a为-1、0或1。因此您可以使用以下代码:

int x;
/* TODO: 将x的值设置为Python/C API传入的Python函数中给予的a值 */
switch (x){
    case -1:
        /* TODO: 当a为-1时 */
        break;
    case 0:
        /* TODO: 当a为0时 */
        break;
    case 1:
        /* TODO: 当a为1时 */
        break;
    default:
        Py_UNREACHABLE()
}

II. Py_ABS(x)

Return the absolute value of x.
New in version 3.3.

译:返回x的绝对值。
此宏在3.3版本中新增。

III. Py_MIN(x, y)

Return the minimum value between x and y.
New in version 3.3.

译:返回xy二者之间的最小值。
此宏在3.3版本中新增。

IV. Py_MAX(x, y)

Return the maximum value between x and y.
New in version 3.3.

译:返回xy二者之间的最大值。
此宏在3.3版本中新增。

V. Py_STRINGIFY(x)

Convert x to a C string. E.g. Py_STRINGIFY(123) returns "123".
New in version 3.4.

译:将x转化为C字符串。例如Py_STRINGIFY(123)返回"123"
此宏在3.4版本中新增。

Py_STRINGIFY宏应接受一个PyObject*作为参数,并且它应指向一个具有__str__方法的Python对象。返回值应为C语言类型。

VI. Py_MEMBER_SIZE(type, member)

Return the size of a structure (type) member in bytes.
New in version 3.6.

译:以字节为单位返回一个type类型的member成员的大小。
此宏在3.6版本中新增。

此宏应接收两个参数,分别为类型和对象指针,返回值为int,单位为byte。

VII. Py_CHARMASK(c)

Argument must be a character or an integer in the range [-128, 127] or [0, 255]. This macro returns c cast to an unsigned char.

译:参数必须为范围在-128<=c<=127或0<=c<=255内的整数或字符。此宏将返回c所构造的unsigned char值。

该宏将字符(char)或无符号字符(unsigned char)统一为无符号字符(unsigned char)。

VIII. Py_GETENV(s)

Like getenv(s), but returns NULL if -E was passed on the command line (i.e. if Py_IgnoreEnvironmentFlag is set).

译:该宏与getenv(s)类似,不同之处在于,当-E选项被指定时(即设置了Py_IgnoreEnvironmentFlag标志),它将返回NULL

该宏用于获取环境信息。有关更多-E选项的详细信息,请见Python官方文档https://docs.python.org/3.7/using/cmdline.html#cmdoption-e(英文)。

IX. Py_UNUSED(arg)

Use this for unused arguments in a function definition to silence compiler warnings, e.g. PyObject* func(PyObject *Py_UNUSED(ignored)).
New in version 3.4.

译:对于函数中未使用的参数,可以使用此宏来消除对应的编译器警告。例如PyObject* func(PyObject *Py_UNUSED(ignored))
该宏在3.4版本中新增。

该宏可用于保留参数。例如,在C扩展函数func(a, b)中,C为其提供了C层面的函数实现。其中a为必要参数,b为保留参数,暂时不使用。则可以有如下函数实现:

PyObject* func(PyObject *a, PyObject *Py_UNUSED(b)){
    /* TODO: 函数体实现 */
}

基本用法为:在函数的定义/声明中,用Py_UNUSED宏包装未使用的参数的形参。

X. PyDoc_STRVAR(name, str)

Creates a variable with name name that can be used in docstrings. If Python is built without docstrings, the value will be empty.
Use PyDoc_STRVAR for docstrings to support building Python without docstrings, as specified in PEP 7.
Example:

译:创建一个名为name的变量,并且可以在文档字符串中使用。如果Python在构建对象时没有文档字符串,那么这个值为空。
根据PEP 7的规定,为文档字符串使用PyDoc_STRVAR来支持在没有文档字符串的情况下构建Python对象。
例如:

PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element.");

static PyMethodDef deque_methods[] = {
    // ...
    {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc},
    // ...
}

获取关于PyDoc_STRVAR的更多信息,详见Python官方文档https://docs.python.org/3.7/c-api/intro.html#c.PyDoc_STRVAR(英文)。

XI. PyDoc_STR(str)

Creates a docstring for the given input string or an empty string if docstrings are disabled.
Use PyDoc_STR in specifying docstrings to support building Python without docstrings, as specified in PEP 7.
Example:

译:使用给定的输入字符串创建一个文档字符串。若文档字符串被禁用,则创建空字符串。
根据PEP 7的规定,在特定的文档字符串中使用PyDoc_STR以支持在未给定文档字符串的情况下创建Python对象。
例如:

static PyMethodDef pysqlite_row_methods[] = {
    {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
        PyDoc_STR("Returns the keys of the row.")},
    {NULL, NULL}
};

获取更多关于PyDoc_STR宏的详细信息,请见Python官方文档https://docs.python.org/3.7/c-api/intro.html#c.PyDoc_STR

对象、类型和引用计数 / Objects, Types and Reference Counts

Most Python/C API functions have one or more arguments as well as a return value of type PyObject*. This type is a pointer to an opaque data type representing an arbitrary Python object. Since all Python object types are treated the same way by the Python language in most situations (e.g., assignments, scope rules, and argument passing), it is only fitting that they should be represented by a single C type. Almost all Python objects live on the heap: you never declare an automatic or static variable of type PyObject, only pointer variables of type PyObject* can be declared. The sole exception are the type objects; since these must never be deallocated, they are typically static PyTypeObject objects.
All Python objects (even Python integers) have a type and a reference count. An object’s type determines what kind of object it is (e.g., an integer, a list, or a user-defined function; there are many more as explained in The standard type hierarchy). For each of the well-known types there is a macro to check whether an object is of that type; for instance, PyList_Check(a) is true if (and only if) the object pointed to by a is a Python list.

译:大多数Python/C API函数都有一个或多个参数以及PyObject*类型的返回值。此类型是指向表示任意Python对象的不透明数据类型的指针。由于在大多数情况下(例如,赋值、范围规则和参数传递),Python对所有Python对象类型的处理方式都是相同的,因此它们应该用一个C类型来表示才合适。几乎所有Python对象都存在于堆上:您将不会声明PyObject类型的自动或静态变量,只能声明PyObject*类型的指针变量。唯一的例外是类型对象;由于这些对象永远不能被释放,它们通常是静态PyTypeObject对象。
所有Python对象(甚至Python整数)都有一个类型和一个引用计数。一个对象的type决定了它是什么类型的对象(例如,一个整数、一个列表或一个用户定义的函数;详见标准类型层次结构一节)。对于每个已知类型,都有一个宏来检查对象是否属于该类型;例如,当(且仅当)a指向的对象是Python列表时,PyList_Check(a)为true。

引用计数 / Reference Counts

The reference count is important because today’s computers have a finite (and often severely limited) memory size; it counts how many different places there are that have a reference to an object. Such a place could be another object, or a global (or static) C variable, or a local variable in some C function. When an object’s reference count becomes zero, the object is deallocated. If it contains references to other objects, their reference count is decremented. Those other objects may be deallocated in turn, if this decrement makes their reference count become zero, and so on. (There’s an obvious problem with objects that reference each other here; for now, the solution is “don’t do that.”)
Reference counts are always manipulated explicitly. The normal way is to use the macro Py_INCREF() to increment an object’s reference count by one, and Py_DECREF() to decrement it by one. The Py_DECREF() macro is considerably more complex than the incref one, since it must check whether the reference count becomes zero and then cause the object’s deallocator to be called. The deallocator is a function pointer contained in the object’s type structure. The type-specific deallocator takes care of decrementing the reference counts for other objects contained in the object if this is a compound object type, such as a list, as well as performing any additional finalization that’s needed. There’s no chance that the reference count can overflow; at least as many bits are used to hold the reference count as there are distinct memory locations in virtual memory (assuming sizeof(Py_ssize_t) >= sizeof(void*)). Thus, the reference count increment is a simple operation.
It is not necessary to increment an object’s reference count for every local variable that contains a pointer to an object. In theory, the object’s reference count goes up by one when the variable is made to point to it and it goes down by one when the variable goes out of scope. However, these two cancel each other out, so at the end the reference count hasn’t changed. The only real reason to use the reference count is to prevent the object from being deallocated as long as our variable is pointing to it. If we know that there is at least one other reference to the object that lives at least as long as our variable, there is no need to increment the reference count temporarily. An important situation where this arises is in objects that are passed as arguments to C functions in an extension module that are called from Python; the call mechanism guarantees to hold a reference to every argument for the duration of the call.
However, a common pitfall is to extract an object from a list and hold on to it for a while without incrementing its reference count. Some other operation might conceivably remove the object from the list, decrementing its reference count and possible deallocating it. The real danger is that innocent-looking operations may invoke arbitrary Python code which could do this; there is a code path which allows control to flow back to the user from a Py_DECREF(), so almost any operation is potentially dangerous.
A safe approach is to always use the generic operations (functions whose name begins with PyObject_, PyNumber_, PySequence_ or PyMapping_). These operations always increment the reference count of the object they return. This leaves the caller with the responsibility to call Py_DECREF() when they are done with the result; this soon becomes second nature.

译:引用计数非常重要,因为今天的计算机内存大小有限(而且常常是严重限制);它计算有多少不同的地方有一个对象的引用。这样的地方可以是另一个对象,或者全局(或静态)C变量,或者是某些C函数中的局部变量。当对象的引用计数变为零时,对象将被解除分配。如果它包含对其他对象的引用,则其引用计数将被减少。如果此递减使它们的引用计数变为零,则这些其他对象可能依次解除分配,依此类推。(这里相互引用的对象有一个明显的问题;目前,解决方案是“不要这样做。”)
引用计数总是被显式操纵的。通常的方法是使用宏Py_INCREF()将对象的引用计数增加1,并使用Py_DECREF()将其减少1。Py_DECREF()宏比Py_INCREF()宏复杂得多,因为它必须检查引用计数是否变为零,然后调用对象的deallocator。deallocator是包含在对象类型结构中的函数指针。特定于类型的deallocator负责减少对象中包含的其他对象的引用计数(如果这是复合对象类型,例如列表),以及执行所需的任何其他终结。引用计数不可能溢出;当虚拟内存中存在不同的内存位置时(假设sizeof(Py_ssize_t)>=sizeof(void*)),用于保存引用计数的位数至少是相同的。因此,参考计数增量是一个简单的操作。
对于包含指向对象的指针的每个局部变量,不必增加对象的引用计数。理论上,当变量指向对象时,对象的引用计数会上升一个,当变量超出范围时,它会下降一个。但是,这两个相互取消,因此在最后的参考计数没有改变。使用引用计数的唯一真正原因是,只要变量指向它,就可以防止对象被解除分配。如果我们知道至少有一个对象引用,并且至少存在于变量的时间内,则无需临时增加引用计数。出现这种情况的一个重要情况是,对象作为参数传递给从Python调用的扩展模块中的C函数;调用机制保证在调用期间保持对每个参数的引用。
然而,一个常见的陷阱是从列表中提取一个对象,并在不增加引用计数的情况下保持一段时间。可以想象,有些其他操作可以从列表中删除对象,减少引用计数,并可能解除对它的处理。真正的危险是,看起来无辜的操作可能调用任意的Python代码,可以这样做;有一个代码路径,允许控件从Py_DECREF()流回用户,因此几乎任何操作都可能会有危险。
安全的方法是始终使用泛型操作(函数的名称以PyObject_PyNumber_PySequence_PyMapping_开头)。这些操作总是增加返回对象的引用计数。这使得调用方在处理结果时有责任调用Py_DECREF();这很快就成为第二种性质。

CPython中的每一个对象都拥有一个引用计数,它代表该对象正在被使用的调用个数。当引用计数为0时,代表目前已没有对该对象的调用,因此将该对象所占用的内存释放掉。
当需要一个Python对象时,Python解释器将在堆上创建一个相应的Python对象并将其引用计数置为1(整数对象是一个例外)。当该对象拥有一个新的调用(例如将其作为参数传入函数)时,它的引用计数自增1;当它的调用结束(例如它作为参数的函数执行完毕返回)时,它的引用计数自减1。当它的引用计数自减得足够多,或被调用了del,导致其引用计数为0时,对象被释放。
前面提到整数对象是一个例外,这是因为需要int对象时不一定会重新申请内存来创建一个对象。Python中,int对象被使用得非常多,如果每次都重新创建,将会消耗大量内存和时间,因此Python设计了一个小整数对象缓冲池,默认范围为闭区间[-256, 255]。在需要int小整数时,将在缓冲池中查找相应的对象。若此对象已经存在,那么将不会创建新的int对象,而会直接使用此对象,并将其引用计数增加1;若不存在,才会创建一个新的int对象,并将其放入小整数对象缓冲池。

引用计数详情 / Reference Count Details

The reference count behavior of functions in the Python/C API is best explained in terms of ownership of references. Ownership pertains to references, never to objects (objects are not owned: they are always shared). “Owning a reference” means being responsible for calling Py_DECREF on it when the reference is no longer needed. Ownership can also be transferred, meaning that the code that receives ownership of the reference then becomes responsible for eventually decref’ing it by calling Py_DECREF() or Py_XDECREF() when it’s no longer needed—or passing on this responsibility (usually to its caller). When a function passes ownership of a reference on to its caller, the caller is said to receive a new reference. When no ownership is transferred, the caller is said to borrow the reference. Nothing needs to be done for a borrowed reference.
Conversely, when a calling function passes in a reference to an object, there are two possibilities: the function steals a reference to the object, or it does not. Stealing a reference means that when you pass a reference to a function, that function assumes that it now owns that reference, and you are not responsible for it any longer.
Few functions steal references; the two notable exceptions are PyList_SetItem() and PyTuple_SetItem(), which steal a reference to the item (but not to the tuple or list into which the item is put!). These functions were designed to steal a reference because of a common idiom for populating a tuple or list with newly created objects; for example, the code to create the tuple (1, 2, “three”) could look like this (forgetting about error handling for the moment; a better way to code this is shown below):

译:Python/C API中函数的引用计数行为最好用引用的所有权来解释。所有权属于引用,而不是对象(对象不具有从属关系:它们总是共享的)。“拥有一个引用”意味着在不再需要引用时负责调用Py_DECREF。所有权也可以转移,这意味着接收引用所有权的代码将负责最终取消引用,方法是在不再需要引用时调用Py_DECREF()Py_XDECREF(),或将此责任传递给它的调用方。当函数将引用的所有权传递给调用方时,调用方被称为接收到新引用。当所有权未转移时,接受者被称为借用引用。对于借用引用,不需要做任何事情。
相反,当调用函数传入对对象的引用时,有两种可能性:函数窃取对对象的引用,或者不窃取。窃取引用意味着,当您将引用传递给函数时,该函数假定它现在拥有该引用,并且您不再对其负责。
很少有函数窃取引用;两个值得注意的例外是PyList_SetItem()PyTuple_SetItem(),它们窃取了对该项的引用(但不是对该项所在的元组或列表的引用!)。这些函数被设计为窃取引用,因为有一种常用的习惯用法,用新创建的对象填充元组或列表;例如,创建元组(1, 2, "three")的代码可能如下所示(暂时忽略错误处理;下面展示了更好的方法):

PyObject *t;

t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));

可以看到,元组t被创建后,在索引为1、2、3的位置都分别赋值。但0项和1项均为PyLong,即Python中的int类型。它们属于借用引用,因为它们在小整数对象缓冲池中,由Python直接管理;而2项的PyUnicode,即Python中的str类型,属于窃取引用,因为您手动创建了一个str对象,并且Python/C API将包含引用计数信息的PyObject*返回给您。

Here, PyLong_FromLong() returns a new reference which is immediately stolen by PyTuple_SetItem(). When you want to keep using an object although the reference to it will be stolen, use Py_INCREF() to grab another reference before calling the reference-stealing function.
Incidentally, PyTuple_SetItem() is the only way to set tuple items; PySequence_SetItem() and PyObject_SetItem() refuse to do this since tuples are an immutable data type. You should only use PyTuple_SetItem() for tuples that you are creating yourself.
Equivalent code for populating a list can be written using PyList_New() and PyList_SetItem().
However, in practice, you will rarely use these ways of creating and populating a tuple or list. There’s a generic function, Py_BuildValue(), that can create most common objects from C values, directed by a format string. For example, the above two blocks of code could be replaced by the following (which also takes care of the error checking):

译:在这里,PyLong_FromLong()返回一个新引用,PyTuple_SetItem()会立即窃取该引用。如果您希望继续使用某个对象,尽管对它的引用将被窃取,请在调用引用窃取函数之前使用Py_INCREF()获取另一个引用。
另外,PyTuple_SetItem()是设置元组元素的唯一方法;PySequence_SetItem()PyObject_SetItem()不能用于设置元组元素,因为元组是不可变的数据类型。对于自己创建的元组,只应使用PyTuple_SetItem()
可以使用PyList_New()PyList_SetItem()编写填充列表的等效代码。
然而,在实践中,您很少使用这些方法来创建和填充元组或列表。有一个通用函数Py_BuildValue(),它可以从C语言值创建最常见的对象,由格式字符串引导。例如,上述两个代码块可以替换为以下代码块(这也负责错误检查):

PyObject *tuple, *list;

tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");

It is much more common to use PyObject_SetItem() and friends with items whose references you are only borrowing, like arguments that were passed in to the function you are writing. In that case, their behaviour regarding reference counts is much saner, since you don’t have to increment a reference count so you can give a reference away (“have it be stolen”). For example, this function sets all items of a list (actually, any mutable sequence) to a given item:

使用PyObject_SetItem()和friends处理引用仅为借用的情况更为常见,例如传递给正在编写的函数的参数。在这种情况下,他们在引用计数方面的行为要理智得多,因为你不必增加引用计数,这样你就可以给出一个引用(被窃取)。例如,该函数将列表中的所有项(实际上是任何可变序列)设置为给定项:

int
set_all(PyObject *target, PyObject *item)
{
    Py_ssize_t i, n;

    n = PyObject_Length(target);
    if (n < 0)
        return -1;
    for (i = 0; i < n; i++) {
        PyObject *index = PyLong_FromSsize_t(i);
        if (!index)
            return -1;
        if (PyObject_SetItem(target, index, item) < 0) {
            Py_DECREF(index);
            return -1;
        }
        Py_DECREF(index);
    }
    return 0;
}

请注意,这里的friends没有翻译为中文,因为编者找不到一个合适、简洁的词语概括它的意思。大致意思为:使用PyObject_SetItem()函数,使一个对象借用另一个对象的引用。这里,被借用的对象即为文档中的friends。

The situation is slightly different for function return values. While passing a reference to most functions does not change your ownership responsibilities for that reference, many functions that return a reference to an object give you ownership of the reference. The reason is simple: in many cases, the returned object is created on the fly, and the reference you get is the only reference to the object. Therefore, the generic functions that return object references, like PyObject_GetItem() and PySequence_GetItem(), always return a new reference (the caller becomes the owner of the reference).
It is important to realize that whether you own a reference returned by a function depends on which function you call only — the plumage (the type of the object passed as an argument to the function) doesn’t enter into it! Thus, if you extract an item from a list using PyList_GetItem(), you don’t own the reference — but if you obtain the same item from the same list using PySequence_GetItem() (which happens to take exactly the same arguments), you do own a reference to the returned object.
Here is an example of how you could write a function that computes the sum of the items in a list of integers; once using PyList_GetItem(), and once using PySequence_GetItem().

译:函数返回值的情况略有不同。虽然将引用传递给大多数函数不会改变您对该引用的所有权,但许多返回对象引用的函数会赋予您对该引用的所有权。原因很简单:在许多情况下,返回的对象是动态创建的,而您得到的引用是对该对象的唯一引用。因此,返回对象引用的通用函数,如PyObject_GetItem()PySequence_GetItem(),总是返回一个新引用(调用方成为引用的所有者)。
重要的是要认识到,你是否拥有一个函数返回的引用取决于你只调用哪个函数——plumage(作为参数传递给函数的对象的类型)不会进入它!因此,如果您使用PyList_GetItem()从列表中提取一个项,那么您并不拥有该引用——但是如果您使用PySequence_GetItem()从同一个列表中获取相同的项(恰好使用完全相同的参数),那么您将确实拥有对返回对象的引用。
下面是一个例子,说明如何编写一个函数来计算整数列表中的项之和;一次使用PyList_GetItem(),一次使用PySequence_GetItem()

Python官方文档中的plumage没有找到合适的义项,因此保留了原文。其详细意义在上文的括号内已经给出。
文档中所指的“(作为参数传递给函数的对象的类型)不会进入它”,意思是将一个对象的类型作为参数传入一个函数时,被传入的类型不会受影响,而承载该类型信息的对象可能受影响。例如,一个Python的int对象a,其定义为a = int,此时a是一个type的对象,它是int类型信息的载体。将a传入函数func(a)后,对象a可能发生改变(例如可能此时a == bool),但最初所带的类型int的信息不会发生改变(否则的话,所有int类型的对象都将等价于一个bool类型的对象,然而这并不可能)。

long
sum_list(PyObject *list)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;

    n = PyList_Size(list);
    if (n < 0)
        return -1; /* Not a list */
    for (i = 0; i < n; i++) {
        item = PyList_GetItem(list, i); /* Can't fail */
        if (!PyLong_Check(item)) continue; /* Skip non-integers */
        value = PyLong_AsLong(item);
        if (value == -1 && PyErr_Occurred())
            /* Integer too big to fit in a C long, bail out */
            return -1;
        total += value;
    }
    return total;
}
long
sum_sequence(PyObject *sequence)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;
    n = PySequence_Length(sequence);
    if (n < 0)
        return -1; /* Has no length */
    for (i = 0; i < n; i++) {
        item = PySequence_GetItem(sequence, i);
        if (item == NULL)
            return -1; /* Not a sequence, or other failure */
        if (PyLong_Check(item)) {
            value = PyLong_AsLong(item);
            Py_DECREF(item);
            if (value == -1 && PyErr_Occurred())
                /* Integer too big to fit in a C long, bail out */
                return -1;
            total += value;
        }
        else {
            Py_DECREF(item); /* Discard reference ownership */
        }
    }
    return total;
}
类型 / Types

There are few other data types that play a significant role in the Python/C API; most are simple C types such as int, long, double and char*. A few structure types are used to describe static tables used to list the functions exported by a module or the data attributes of a new object type, and another is used to describe the value of a complex number. These will be discussed together with the functions that use them.

译:在Python/C API中起重要作用的其他数据类型很少;大多数都是简单的C类型,比如intlongdoublechar*。一些结构类型用于描述静态表,用于列出模块导出的函数或新对象类型的数据属性,另一种结构类型用于描述复数的值。这些将与使用它们的函数一起讨论。

需要提出的是,您可能不了解Python中的复数。复数是一个数学概念,复数 w = a + b i w=a+bi w=a+bi,其中 w w w为一个复数, a a a b b b为常实数, i i i为虚数单位,定义为 i = − 1 i=\sqrt-1 i= 1。但在Python中,复数的定义为w=a+bj,除了将i换为j以外,与数学上的定义基本相符。
Python中,根据数学,将复数分为实部(real part)和虚部(imaginary part)。它们之间是加法关系。

异常 / Exceptions

The Python programmer only needs to deal with exceptions if specific error handling is required; unhandled exceptions are automatically propagated to the caller, then to the caller’s caller, and so on, until they reach the top-level interpreter, where they are reported to the user accompanied by a stack traceback.
For C programmers, however, error checking always has to be explicit. All functions in the Python/C API can raise exceptions, unless an explicit claim is made otherwise in a function’s documentation. In general, when a function encounters an error, it sets an exception, discards any object references that it owns, and returns an error indicator. If not documented otherwise, this indicator is either NULL or -1, depending on the function’s return type. A few functions return a Boolean true/false result, with false indicating an error. Very few functions return no explicit error indicator or have an ambiguous return value, and require explicit testing for errors with PyErr_Occurred(). These exceptions are always explicitly documented.
Exception state is maintained in per-thread storage (this is equivalent to using global storage in an unthreaded application). A thread can be in one of two states: an exception has occurred, or not. The function PyErr_Occurred() can be used to check for this: it returns a borrowed reference to the exception type object when an exception has occurred, and NULL otherwise. There are a number of functions to set the exception state: PyErr_SetString() is the most common (though not the most general) function to set the exception state, and PyErr_Clear() clears the exception state.
The full exception state consists of three objects (all of which can be NULL): the exception type, the corresponding exception value, and the traceback. These have the same meanings as the Python result of sys.exc_info(); however, they are not the same: the Python objects represent the last exception being handled by a Python try … except... statement, while the C level exception state only exists while an exception is being passed on between C functions until it reaches the Python bytecode interpreter’s main loop, which takes care of transferring it to sys.exc_info() and friends.
Note that starting with Python 1.5, the preferred, thread-safe way to access the exception state from Python code is to call the function sys.exc_info(), which returns the per-thread exception state for Python code. Also, the semantics of both ways to access the exception state have changed so that a function which catches an exception will save and restore its thread’s exception state so as to preserve the exception state of its caller. This prevents common bugs in exception handling code caused by an innocent-looking function overwriting the exception being handled; it also reduces the often unwanted lifetime extension for objects that are referenced by the stack frames in the traceback.
As a general principle, a function that calls another function to perform some task should check whether the called function raised an exception, and if so, pass the exception state on to its caller. It should discard any object references that it owns, and return an error indicator, but it should not set another exception — that would overwrite the exception that was just raised, and lose important information about the exact cause of the error.
A simple example of detecting exceptions and passing them on is shown in the sum_sequence() example above. It so happens that this example doesn’t need to clean up any owned references when it detects an error. The following example function shows some error cleanup. First, to remind you why you like Python, we show the equivalent Python code:

译:Python程序员只需要在需要特定错误处理时处理异常;未经处理的异常会自动传播到调用者,然后传播到调用者的调用者,依此类推,直到它们到达解释器调用链的最顶端,并在那里向用户报告,同时进行堆栈回溯。
然而,对于C程序员来说,错误检查必须是明确的。Python/C API中的所有函数都可能引发异常,除非函数的文档中另有明确声明。通常,当函数遇到错误时,它会设置一个异常,放弃它拥有的任何对象引用,并返回一个错误指示符。如果没有其他文档记录,则该指示符要么为NULL,要么为-1,具体取决于函数的返回类型。一些函数返回布尔true/false结果,false表示错误。很少有函数不返回显式错误指示符或返回值不明确,并且需要使用PyErr_Occurrent()显式测试错误。这些例外情况总是明确记录在案。
异常状态在每线程存储中维护(这相当于在单线程应用程序中使用全局存储)。线程可以处于两种状态之一:是否发生异常。函数PyErr_Occurrent()可用于检查这一点:当发生异常时,它返回对异常类型对象的借用引用,否则返回NULL。有许多函数可以设置异常状态:PyErr_SetString()是设置异常状态的最常用(尽管不是最通用)函数,PyErr_Clear()清除异常状态。
完整异常状态由三个对象组成(所有对象都可以为NULL):异常类型、相应的异常值和回溯。这些与Python中调用sys.exc_info()所得的结果具有相同的含义;然而,从本质上讲,它们并不相同:Python对象表示Python的try…except...语句处理的最后一个异常,而C层面的异常状态仅在异常在C函数之间传递时存在,直到它到达Python字节码解释器的主循环,该循环负责将其传输到sys.exc_info()和friends。
请注意,从Python 1.5开始,从Python代码访问异常状态的首选线程安全方式是调用sys.exc_info()函数,它返回Python代码的每个线程异常状态。此外,访问异常状态的两种方法的含义都发生了变化,因此捕获异常的函数将保存并恢复其线程的异常状态,从而保留其调用方的异常状态并返回给其调用者。这可以防止由于看似不存在问题的函数覆盖正在处理的异常而导致异常处理代码中的常见错误;它还减少了回溯中堆栈帧引用的对象通常不需要的生命周期延长。
一般来说,调用另一个函数来执行某项任务的函数应该检查被调用函数是否引发了异常,如果是,则将异常状态传递给其调用方。它应该放弃它拥有的任何对象引用,并返回一个错误指示,但不应该设置另一个异常——这将覆盖刚刚引发的异常,并丢失有关错误确切原因的重要信息。
上面的sum_sequence()示例显示了一个检测异常并将其传递的简单示例。碰巧的是,这个例子在检测到错误时不需要清理任何拥有的引用。下面的示例函数显示了一些错误清理。首先,为了提醒您为什么喜欢Python,我们展示了等效的Python代码:

def incr_item(dict, key):
    try:
        item = dict[key]
    except KeyError:
        item = 0
    dict[key] = item + 1

Here is the corresponding C code, in all its glory:

下面是相应的C语言代码,十分精彩:

int
incr_item(PyObject *dict, PyObject *key)
{
    /* Objects all initialized to NULL for Py_XDECREF */
    PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
    int rv = -1; /* Return value initialized to -1 (failure) */

    item = PyObject_GetItem(dict, key);
    if (item == NULL) {
        /* Handle KeyError only: */
        if (!PyErr_ExceptionMatches(PyExc_KeyError))
            goto error;

        /* Clear the error and use zero: */
        PyErr_Clear();
        item = PyLong_FromLong(0L);
        if (item == NULL)
            goto error;
    }
    const_one = PyLong_FromLong(1L);
    if (const_one == NULL)
        goto error;

    incremented_item = PyNumber_Add(item, const_one);
    if (incremented_item == NULL)
        goto error;

    if (PyObject_SetItem(dict, key, incremented_item) < 0)
        goto error;
    rv = 0; /* Success */
    /* Continue with cleanup code */

 error:
    /* Cleanup code, shared by success and failure path */

    /* Use Py_XDECREF() to ignore NULL references */
    Py_XDECREF(item);
    Py_XDECREF(const_one);
    Py_XDECREF(incremented_item);

    return rv; /* -1 for error, 0 for success */

This example represents an endorsed use of the goto statement in C! It illustrates the use of PyErr_ExceptionMatches() and PyErr_Clear() to handle specific exceptions, and the use of Py_XDECREF() to dispose of owned references that may be NULL (note the ‘X’ in the name; Py_DECREF() would crash when confronted with a NULL reference). It is important that the variables used to hold owned references are initialized to NULL for this to work; likewise, the proposed return value is initialized to -1 (failure) and only set to success after the final call made is successful.

译:这个例子代表了在C中对goto语句的支持!它演示了如何使用PyErr_ExceptionMatches()PyErr_Clear()来处理特定的异常,以及如何使用Py_XDECREF()来处理可能为NULL的自有引用(请注意名称中的“X”;Py_DECREF()在遇到NULL引用时会引起应用程序崩溃)。重要的是,用于保存所拥有引用的变量被初始化为NULL,这样才能工作;同样,建议的返回值被初始化为-1(异常),只有在最后一次调用成功后才设置为success。

嵌入Python / Embedding Python

The one important task that only embedders (as opposed to extension writers) of the Python interpreter have to worry about is the initialization, and possibly the finalization, of the Python interpreter. Most functionality of the interpreter can only be used after the interpreter has been initialized.
The basic initialization function is Py_Initialize(). This initializes the table of loaded modules, and creates the fundamental modules builtins, __main__, and sys. It also initializes the module search path (sys.path).
Py_Initialize() does not set the “script argument list” (sys.argv). If this variable is needed by Python code that will be executed later, it must be set explicitly with a call to PySys_SetArgvEx(argc, argv, updatepath) after the call to Py_Initialize().
On most systems (in particular, on Unix and Windows, although the details are slightly different), Py_Initialize() calculates the module search path based upon its best guess for the location of the standard Python interpreter executable, assuming that the Python library is found in a fixed location relative to the Python interpreter executable. In particular, it looks for a directory named lib/pythonX.Y relative to the parent directory where the executable named python is found on the shell command search path (the environment variable PATH).
For instance, if the Python executable is found in /usr/local/bin/python, it will assume that the libraries are in /usr/local/lib/pythonX.Y. (In fact, this particular path is also the “fallback” location, used when no executable file named python is found along PATH.) The user can override this behavior by setting the environment variable PYTHONHOME, or insert additional directories in front of the standard path by setting PYTHONPATH.
The embedding application can steer the search by calling Py_SetProgramName(file) before calling Py_Initialize(). Note that PYTHONHOME still overrides this and PYTHONPATH is still inserted in front of the standard path. An application that requires total control has to provide its own implementation of Py_GetPath(), Py_GetPrefix(), Py_GetExecPrefix(), and Py_GetProgramFullPath() (all defined in Modules/getpath.c).
Sometimes, it is desirable to “uninitialize” Python. For instance, the application may want to start over (make another call to Py_Initialize()) or the application is simply done with its use of Python and wants to free memory allocated by Python. This can be accomplished by calling Py_FinalizeEx(). The function Py_IsInitialized() returns true if Python is currently in the initialized state. More information about these functions is given in a later chapter. Notice that Py_FinalizeEx() does not free all memory allocated by the Python interpreter, e.g. memory allocated by extension modules currently cannot be released.

译:只有Python解释器的嵌入者(而不是扩展编写者)需要担心的一项重要任务是Python解释器的初始化,可能还有最终完成。解释器的大部分功能只能在解释器初始化后使用。
基本的初始化函数是Py_Initialize()。这将初始化加载的模块表,并创建基本模块内置项、主模块和系统。它还初始化模块搜索路径(sys.path)。
Py_Initialize()不设置“脚本参数列表”(sys.argv)。如果稍后执行的Python代码需要此变量,则必须在调用Py_Initialize()之后通过调用PySys_SetArgvEx(argc, argv, updatepath)显式设置此变量。
在大多数系统上(尤其是在Unix和Windows上,尽管细节略有不同),Py_Initialize()根据其对标准Python解释器可执行文件位置的最佳猜测来计算模块搜索路径,前提是Python库位于相对于Python解释器可执行文件的固定位置。特别是,它会查找名为lib/pythonX.Y的目录。相对于父目录,在shell命令搜索路径(环境变量路径)上可以找到名为python的可执行文件。
例如,如果Python可执行文件位于/usr/local/bin/Python中,它将假定库位于/usr/local/lib/pythonX.Y中。(实际上,这个特定的路径也是“后备”位置,在路径上找不到名为python的可执行文件时使用。)用户可以通过设置环境变量PYTHONHOME来覆盖此行为,或者通过设置PYTHONPATH在标准路径前面插入其他目录。
嵌入应用程序可以通过在调用Py_Initialize()之前调用Py_SetProgramName(file)来引导搜索。请注意,PYTHONHOME仍然覆盖该路径,PYTHONPATH仍然插入到标准路径的前面。需要完全控制的应用程序必须提供自己的Py_GetPath()Py_GetPrefix()Py_GetExecPrefix()Py_GetProgramFullPath()实现(都在Modules/GetPath.c中定义)。
有时,希望“取消初始化”Python。例如,应用程序可能希望重新开始(再次调用Py_Initialize()),或者应用程序只需使用Python就可以完成,并希望释放Python分配的内存。这可以通过调用Py_FinalizeEx()来实现。如果Python当前处于初始化状态,函数Py_IsInitialized()将返回true。关于这些函数的更多信息将在后面的章节中给出。请注意,Py_FinalizeEx()不会释放Python解释器分配的所有内存,例如,当前无法释放扩展模块分配的内存。

调试生成 / Debugging Builds

Python can be built with several macros to enable extra checks of the interpreter and extension modules. These checks tend to add a large amount of overhead to the runtime so they are not enabled by default.
A full list of the various types of debugging builds is in the file Misc/SpecialBuilds.txt in the Python source distribution. Builds are available that support tracing of reference counts, debugging the memory allocator, or low-level profiling of the main interpreter loop. Only the most frequently-used builds will be described in the remainder of this section.
Compiling the interpreter with the Py_DEBUG macro defined produces what is generally meant by “a debug build” of Python. Py_DEBUG is enabled in the Unix build by adding --with-pydebug to the ./configure command. It is also implied by the presence of the not-Python-specific _DEBUG macro. When Py_DEBUG is enabled in the Unix build, compiler optimization is disabled.
In addition to the reference count debugging described below, the following extra checks are performed:

  • Extra checks are added to the object allocator.
  • Extra checks are added to the parser and compiler.
  • Downcasts from wide types to narrow types are checked for loss of information.
  • A number of assertions are added to the dictionary and set implementations. In addition, the set object acquires a test_c_api() method.
  • Sanity checks of the input arguments are added to frame creation.
  • The storage for ints is initialized with a known invalid pattern to catch reference to uninitialized digits.
  • Low-level tracing and extra exception checking are added to the runtime virtual machine.
  • Extra checks are added to the memory arena implementation.
  • Extra debugging is added to the thread module.

There may be additional checks not mentioned here.
Defining Py_TRACE_REFS enables reference tracing. When defined, a circular doubly linked list of active objects is maintained by adding two extra fields to every PyObject. Total allocations are tracked as well. Upon exit, all existing references are printed. (In interactive mode this happens after every statement run by the interpreter.) Implied by Py_DEBUG.
Please refer to Misc/SpecialBuilds.txt in the Python source distribution for more detailed information.

译:Python可以用几个宏来构建,以便对解释器和扩展模块进行额外检查。这些检查往往会给运行时增加大量开销,因此默认情况下不会启用它们。
文件Misc/SpecialBuilds.txt中有各种调试版本的完整列表。Python源代码发行版中有详细信息。可以使用支持跟踪引用计数、调试内存分配器或对主解释器循环进行低级评测的构建。本节的其余部分将只介绍最常用的版本。
使用定义的Py_DEBUG宏编译解释器会产生Python的“调试构建”。Py_DEBUG通过在Unix构建中添加--with pydebug来启用/配置命令。非Python特定的_DEBUG宏的存在也暗示了这一点。在Unix版本中启用Py_DEBUG调试时,编译器优化将被禁用。
除了下面描述的参考计数调试外,还执行以下额外检查:
额外的检查被添加到对象分配器中。

  • 额外的检查被添加到解析器和编译器中。
  • 检查从宽类型到窄类型的降级是否丢失信息。
  • 字典和集合实现中添加了许多断言。此外,set对象获取一个test_c_api()方法。
  • 输入参数的健全性检查被添加到框架创建中。
  • int类型对象的存储使用已知的无效模式进行初始化,以捕获对未初始化数字的引用。
  • 低级别跟踪和额外的异常检查被添加到运行时虚拟机中。
  • 额外的检查被添加到memory arena实现中。
  • 额外的调试被添加到线程模块中。
    这里可能没有提到其他检查。
    定义Py_TRACE_REFS可启用引用跟踪。定义后,通过向每个PyObject添加两个额外字段来维护活动对象的循环双链接列表。总分配也会被追踪。退出后,将打印所有现有参考。(在交互模式下,这发生在解释器运行的每个语句之后。)由Py_DEBUG作为标志。
    请参考Misc/SpecialBuilds.txt。有关详细信息,请参阅Python源代码发行版中的详细信息。

编者技术有限,若有不足敬请指教!


参考资料 / References

[1]: Python Software Foundation [Z], Introduction – Python 3.7.12 documentation. (2021) 2021.09.06/2022.01.26.
[2]: Guido van Rossum, Barry Warsaw, [Z], PEP 7 – Style Guide for C Code. (2001, from https://www.python.org/dev/peps/pep-0007/). 2001.07.05/2022.01.26.
[3]: ultraji [Z], 《errno.h 详解》. (2018, from https://blog.csdn.net/qq_23827747/article/details/79753552?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164318105616780265455708%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164318105616780265455708&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-79753552.first_rank_v2_pc_rank_v29&utm_term=errno.h&spm=1018.2226.3001.4187) 2018.03.30/2022.01.26.
[4]: 薛定谔又生又死的猫 [Z], limits.h. (2015 https://blog.csdn.net/acmicpc123/article/details/50208757?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164318132916781685332754%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164318132916781685332754&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-50208757.first_rank_v2_pc_rank_v29&utm_term=limits.h&spm=1018.2226.3001.4187) 2015.12.07/2022.01.26.

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值