简介 — Python 文档
介绍
Application Programmer's Interface to Python 使 C 和 C++ 程序员可以在各种级别访问 Python 解释器。 该 API 同样可以从 C++ 使用,但为简洁起见,它通常被称为 Python/C API。 使用 Python/C API 有两个根本不同的原因。 第一个原因是为了特定目的编写扩展模块; 这些是扩展 Python 解释器的 C 模块。 这可能是最常见的用途。 第二个原因是在更大的应用程序中使用 Python 作为组件; 这种技术通常被称为应用程序中的 嵌入 Python。
编写扩展模块是一个相对容易理解的过程,其中“食谱”方法效果很好。 有几种工具可以在一定程度上自动化该过程。 虽然人们自 Python 早期存在以来就将其嵌入到其他应用程序中,但嵌入 Python 的过程不如编写扩展那么简单。
许多 API 函数都非常有用,无论您是嵌入还是扩展 Python; 此外,大多数嵌入 Python 的应用程序也需要提供自定义扩展,因此在尝试将 Python 嵌入到实际应用程序中之前熟悉编写扩展可能是个好主意。
编码标准
如果您正在编写包含在 CPython 中的 C 代码,则 必须 遵循 PEP 7 中定义的准则和标准。 无论您贡献的 Python 版本如何,这些指南都适用。 您自己的第三方扩展模块不需要遵循这些约定,除非您最终希望将它们贡献给 Python。
包含文件
使用 Python/C API 所需的所有函数、类型和宏定义都通过以下行包含在您的代码中:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
这意味着包含以下标准标头:<stdio.h>
、<string.h>
、<errno.h>
、<limits.h>
、<assert.h>
和 <stdlib.h>
(如果可供使用的话)。
笔记
由于 Python 可能会定义一些影响某些系统上的标准头文件的预处理器定义,因此在包含任何标准头文件之前,您 必须 包含 Python.h
。
建议在包含 Python.h
之前始终定义 PY_SSIZE_T_CLEAN
。 有关此宏的说明,请参阅 解析参数和构建值 。
Python.h 定义的所有用户可见名称(除了由包含的标准头文件定义的名称)都具有前缀 Py
或 _Py
之一。 以 _Py
开头的名称供 Python 实现内部使用,不应由扩展编写者使用。 结构成员名称没有保留前缀。
笔记
用户代码不应定义以 Py
或 _Py
开头的名称。 这会使读者感到困惑,并危及用户代码到未来 Python 版本的可移植性,这些版本可能会定义以这些前缀之一开头的其他名称。
头文件通常与 Python 一起安装。 在 Unix 上,它们位于目录 prefix/include/pythonversion/
和 exec_prefix/include/pythonversion/
中,其中 prefix
和 exec_prefix
是由 Python 的 configure 脚本和 version 的相应参数定义为 '%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。
有用的宏
Python 头文件中定义了几个有用的宏。 许多被定义为更接近它们有用的地方(例如 Py_RETURN_NONE)。 此处定义了其他更通用的实用程序。 这不一定是完整的列表。
- Py_UNREACHABLE()
当您有不希望到达的代码路径时,请使用此选项。 例如,在
switch
语句中的default:
子句中,所有可能的值都包含在case
语句中。 在您可能想要发出assert(0)
或abort()
呼叫的地方使用它。3.7 版中的新功能。
- Py_ABS(x)
返回
x
的绝对值。3.3 版中的新功能。
- Py_MIN(x, y)
返回
x
和y
之间的最小值。3.3 版中的新功能。
- Py_MAX(x, y)
返回
x
和y
之间的最大值。3.3 版中的新功能。
- Py_STRINGIFY(x)
将
x
转换为 C 字符串。 例如Py_STRINGIFY(123)
返回"123"
。3.4 版中的新功能。
- Py_MEMBER_SIZE(type, member)
以字节为单位返回结构 (
type
)member
的大小。3.6 版中的新功能。
- Py_CHARMASK(c)
- 参数必须是 [-128, 127] 或 [0, 255] 范围内的字符或整数。 此宏将
c
转换为unsigned char
。
- Py_GETENV(s)
- 与
getenv(s)
类似,但如果 -E 在命令行上传递(即,返回NULL
如果设置了Py_IgnoreEnvironmentFlag
)。
- Py_UNUSED(arg)
将此用于函数定义中未使用的参数以消除编译器警告。 示例:
int func(int a, int Py_UNUSED(b)) { return a; }
。3.4 版中的新功能。
- Py_DEPRECATED(version)
将此用于已弃用的声明。 宏必须放在符号名称之前。
例子:
Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
3.8 版更改:添加了 MSVC 支持。
- PyDoc_STRVAR(name, str)
创建一个可以在文档字符串中使用的名称为
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_STR(str)
如果文档字符串被禁用,则为给定的输入字符串或空字符串创建一个文档字符串。
使用 PyDoc_STR 指定文档字符串以支持构建没有文档字符串的 Python,如 PEP 7 中所述。
例子:
static PyMethodDef pysqlite_row_methods[] = { {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS, PyDoc_STR("Returns the keys of the row.")}, {NULL, NULL} };
对象、类型和引用计数
大多数 Python/C API 函数都有一个或多个参数以及类型为 PyObject* 的返回值。 此类型是指向表示任意 Python 对象的不透明数据类型的指针。 由于 Python 语言在大多数情况下(例如,赋值、作用域规则和参数传递)以相同的方式处理所有 Python 对象类型,因此它们应该由单个 C 类型表示才合适。 几乎所有 Python 对象都存在于堆中:您永远不会声明类型为 PyObject 的自动或静态变量,只能声明类型为 PyObject* 的指针变量。 唯一的例外是类型对象; 由于这些永远不能被释放,它们通常是静态的 PyTypeObject 对象。
所有 Python 对象(甚至 Python 整数)都有一个 type 和一个 引用计数 。 一个对象的类型决定了它是什么类型的对象(例如,一个整数、一个列表或一个用户定义的函数;在标准类型层次结构中解释了更多)。 对于每个众所周知的类型,都有一个宏来检查对象是否属于该类型; 例如,当(且仅当)由 a 指向的对象是 Python 列表时,PyList_Check(a)
为真。
引用计数
引用计数很重要,因为今天的计算机的内存大小是有限的(而且通常是非常有限的); 它计算有多少个不同的地方引用了一个对象。 这样的地方可以是另一个对象,或全局(或静态)C 变量,或某些 C 函数中的局部变量。 当一个对象的引用计数变为零时,该对象被释放。 如果它包含对其他对象的引用,则它们的引用计数递减。 如果此递减使它们的引用计数变为零,则这些其他对象可能会依次释放,依此类推。 (这里相互引用的对象有一个明显的问题;现在,解决方案是“不要那样做”。)
引用计数总是被显式操作。 通常的方法是使用宏 Py_INCREF() 将对象的引用计数加一,并使用 Py_DECREF() 将其减一。 Py_DECREF() 宏比 incref 复杂得多,因为它必须检查引用计数是否为零,然后调用对象的释放器。 解除分配器是一个包含在对象类型结构中的函数指针。 如果对象是复合对象类型(例如列表),则特定于类型的解除分配器负责递减包含在对象中的其他对象的引用计数,并执行所需的任何其他终结。 引用计数不可能溢出; 用于保存引用计数的位至少与虚拟内存中存在不同的内存位置一样多(假设 sizeof(Py_ssize_t) >= sizeof(void*)
)。 因此,引用计数增量是一个简单的操作。
没有必要为每个包含指向对象的指针的局部变量增加对象的引用计数。 理论上,当变量指向它时,对象的引用计数增加一,当变量超出范围时,它的引用计数减少一。 然而,这两个相互抵消,所以最后引用计数没有改变。 使用引用计数的唯一真正原因是只要我们的变量指向它,就可以防止对象被释放。 如果我们知道至少有一个对对象的引用至少与我们的变量一样长,那么就不需要临时增加引用计数。 出现这种情况的一个重要情况是在从 Python 调用的扩展模块中作为参数传递给 C 函数的对象; 调用机制保证在调用期间保持对每个参数的引用。
然而,一个常见的陷阱是从列表中提取一个对象并保留它一段时间而不增加它的引用计数。 一些其他操作可能会从列表中删除对象,减少它的引用计数并可能释放它。 真正的危险在于,看似无辜的操作可能会调用可以执行此操作的任意 Python 代码; 有一个代码路径允许控制从 Py_DECREF() 流回用户,因此几乎任何操作都有潜在危险。
一种安全的方法是始终使用通用操作(名称以 PyObject_
、PyNumber_
、PySequence_
或 PyMapping_
开头的函数)。 这些操作总是增加它们返回的对象的引用计数。 这使得调用者有责任在完成结果后调用 Py_DECREF(); 这很快成为第二天性。
引用计数详细信息
Python/C API 中函数的引用计数行为最好通过 引用的所有权 来解释。 所有权属于引用,而不是对象(对象不属于自己:它们总是共享的)。 “拥有引用”意味着在不再需要引用时负责调用 Py_DECREF。 所有权也可以转移,这意味着获得引用所有权的代码负责最终通过调用 Py_DECREF() 或 Py_XDECREF() 来对其进行解引用,当它不再是需要——或传递这个责任(通常给它的调用者)。 当一个函数将引用的所有权传递给它的调用者时,调用者被称为接收一个 new 引用。 当没有所有权转移时,调用者被称为 借用 引用。 对于借来的参考文献,无需做任何事情。
相反,当调用函数传入对象的引用时,有两种可能性:函数 窃取 对象的引用,或者不窃取。 窃取引用 意味着当您将引用传递给函数时,该函数假定它现在拥有该引用,并且您不再对其负责。
很少有函数会窃取引用; 两个值得注意的例外是 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"));
这里,PyLong_FromLong() 返回一个新引用,该引用立即被 PyTuple_SetItem() 窃取。 当你想继续使用一个对象,尽管它的引用会被盗,在调用引用窃取函数之前使用 Py_INCREF() 获取另一个引用。
顺便说一下, PyTuple_SetItem() 是 only 设置元组项的方法; 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");
更常见的是使用 PyObject_SetItem() 和朋友,这些项目的引用只是借用,例如传递给您正在编写的函数的参数。 在这种情况下,他们关于引用计数的行为要理智得多,因为您不必增加引用计数,这样您就可以放弃引用(“让它被盗”)。 例如,此函数将列表的所有项目(实际上是任何可变序列)设置为给定项目:
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;
}
函数返回值的情况略有不同。 虽然传递对大多数函数的引用不会改变您对该引用的所有权责任,但许多返回对象引用的函数会赋予您引用的所有权。 原因很简单:在很多情况下,返回的对象是动态创建的,你得到的引用是对象的唯一引用。 因此,返回对象引用的通用函数,如 PyObject_GetItem() 和 PySequence_GetItem(),总是返回一个新的引用(调用者成为引用的所有者)。
重要的是要意识到你是否拥有一个函数返回的引用取决于你只调用哪个函数——羽毛(作为参数传递给函数的对象的类型)doesn' t 进入它! 因此,如果您使用 PyList_GetItem() 从列表中提取一个项目,您不拥有该引用 - 但如果您使用 [ 从同一列表中获取相同的项目X404X]PySequence_GetItem()(恰好采用完全相同的参数),您确实拥有对返回对象的引用。
下面是一个示例,说明如何编写一个函数来计算整数列表中的项之和; 一次使用 PyList_GetItem(),一次使用 PySequence_GetItem()。
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;
}
类型
很少有其他数据类型在 Python/C API 中发挥重要作用; 大多数是简单的 C 类型,例如 int、long、double 和 char*。 一些结构类型用于描述静态表,用于列出模块导出的功能或新对象类型的数据属性,另一种用于描述复数的值。 这些将与使用它们的函数一起讨论。
例外
Python 程序员只需要在需要特定的错误处理时处理异常; 未处理的异常会自动传播到调用者,然后传播到调用者的调用者,依此类推,直到它们到达顶级解释器,在那里它们被报告给用户并伴随堆栈回溯。
然而,对于 C 程序员来说,错误检查总是必须是显式的。 Python/C API 中的所有函数都可能引发异常,除非在函数文档中另有明确声明。 一般来说,当一个函数遇到错误时,它会设置一个异常,丢弃它拥有的任何对象引用,并返回一个错误指示符。 如果没有另外记录,此指示符是 NULL
或 -1
,具体取决于函数的返回类型。 一些函数返回布尔值 true/false 结果,false 表示错误。 很少有函数不返回显式错误指示符或具有不明确的返回值,并且需要使用 PyErr_Occurred() 显式测试错误。 这些异常总是被明确记录下来。
异常状态在每线程存储中维护(这相当于在非线程应用程序中使用全局存储)。 线程可以处于以下两种状态之一:发生异常或未发生异常。 函数 PyErr_Occurred() 可用于检查这一点:它在发生异常时返回对异常类型对象的借用引用,否则返回 NULL
。 设置异常状态的函数有很多: PyErr_SetString() 是最常见(虽然不是最通用)的设置异常状态的函数,PyErr_Clear() 清除异常状态。
完整的异常状态由三个对象组成(都可以是NULL
):异常类型、对应的异常值和回溯。 这些与sys.exc_info()
的Python结果含义相同; 然而,它们并不相同:Python 对象表示由 Python try … except 语句处理的最后一个异常,而 C 级异常状态仅在异常发生时存在在 C 函数之间传递,直到它到达 Python 字节码解释器的主循环,它负责将它传输到 sys.exc_info()
和朋友。
请注意,从 Python 1.5 开始,从 Python 代码访问异常状态的首选线程安全方法是调用函数 sys.exc_info(),该函数返回 Python 代码的每个线程异常状态. 此外,访问异常状态的两种方式的语义都发生了变化,因此捕获异常的函数将保存和恢复其线程的异常状态,从而保留其调用者的异常状态。 这可以防止异常处理代码中由看似无辜的函数覆盖正在处理的异常引起的常见错误; 它还减少了回溯中堆栈帧引用的对象通常不需要的生命周期延长。
作为一般原则,调用另一个函数来执行某些任务的函数应该检查被调用的函数是否引发了异常,如果是,则将异常状态传递给其调用者。 它应该丢弃它拥有的任何对象引用,并返回一个错误指示符,但它应该 not 设置另一个异常——这将覆盖刚刚引发的异常,并丢失有关确切原因的重要信息错误。
上面的 sum_sequence()
示例中显示了检测异常并将其传递的简单示例。 碰巧这个例子在检测到错误时不需要清理任何拥有的引用。 以下示例函数显示了一些错误清理。 首先,为了提醒你为什么喜欢 Python,我们展示了等效的 Python 代码:
def incr_item(dict, key):
try:
item = dict[key]
except KeyError:
item = 0
dict[key] = item + 1
这是相应的 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 */
}
这个例子代表了 C 中 goto
语句的认可使用! 它说明了使用 PyErr_ExceptionMatches() 和 PyErr_Clear() 来处理特定异常,以及使用 Py_XDECREF() 来处理可能是NULL
(注意名称中的 'X'
;Py_DECREF() 在遇到 NULL
引用时会崩溃)。 重要的是用于保存拥有引用的变量被初始化为 NULL
以使其工作; 同样,建议的返回值被初始化为 -1
(失败),并且只有在最终调用成功后才设置为成功。
嵌入 Python
只有 Python 解释器的嵌入者(而不是扩展编写者)需要担心的一项重要任务是 Python 解释器的初始化,也可能是最终确定。 解释器的大部分功能只能在解释器初始化后才能使用。
基本的初始化函数是Py_Initialize()。 这将初始化加载模块的表,并创建基本模块 builtins、__main__ 和 sys。 它还初始化模块搜索路径 (sys.path
)。
Py_Initialize() 不设置“脚本参数列表”(sys.argv
)。 如果稍后将执行的 Python 代码需要此变量,则必须在调用 Py_Initialize() 后通过调用 PySys_SetArgvEx(argc, argv, updatepath)
显式设置它。
在大多数系统上(特别是在 Unix 和 Windows 上,尽管细节略有不同),Py_Initialize() 根据其对标准 Python 解释器可执行文件位置的最佳猜测来计算模块搜索路径,假设Python 库位于相对于 Python 解释器可执行文件的固定位置。 特别是,它会在 shell 命令搜索路径(环境变量 PATH
)。
例如,如果在 /usr/local/bin/python
中找到 Python 可执行文件,它将假定库在 /usr/local/lib/pythonX.Y
中。 (事实上,这个特定的路径也是“回退”位置,当在 PATH
上没有找到名为 python
的可执行文件时使用。)用户可以覆盖这个行为通过设置环境变量 PYTHONHOME,或者通过设置 PYTHONPATH 在标准路径前面插入额外的目录。
嵌入应用程序可以通过调用 Py_SetProgramName(file)
before 调用 Py_Initialize() 来引导搜索。 请注意, PYTHONHOME 仍然会覆盖它,并且 PYTHONPATH 仍然插入在标准路径的前面。 需要完全控制的应用程序必须提供自己的 Py_GetPath()、Py_GetPrefix()、Py_GetExecPrefix() 和 PyPath(GetProgram)Full 的实现(均在 Modules/getpath.c
中定义)。
有时,需要“取消初始化”Python。 例如,应用程序可能想要重新开始(再次调用 Py_Initialize()),或者应用程序只是使用 Python 完成并想要释放由 Python 分配的内存。 这可以通过调用 Py_FinalizeEx() 来完成。 如果 Python 当前处于初始化状态,则函数 Py_IsInitialized() 返回 true。 关于这些函数的更多信息在后面的章节中给出。 请注意, Py_FinalizeEx() 不会 不 释放 Python 解释器分配的所有内存,例如 扩展模块分配的内存当前无法释放。
调试构建
Python 可以使用多个宏来构建,以启用对解释器和扩展模块的额外检查。 这些检查往往会给运行时增加大量开销,因此默认情况下不会启用它们。
各种类型的调试构建的完整列表在 Python 源代码分发中的文件 Misc/SpecialBuilds.txt
中。 可以使用支持跟踪引用计数、调试内存分配器或主解释器循环的低级分析的版本。 本节的其余部分将仅描述最常用的构建。
使用定义的 Py_DEBUG
宏编译解释器会产生通常所说的 Python 的“调试版本”。 Py_DEBUG
在 Unix 版本中通过将 --with-pydebug
添加到 ./configure
命令来启用。 非 Python 特定的 _DEBUG
宏的存在也暗示了这一点。 在 Unix 版本中启用 Py_DEBUG
时,将禁用编译器优化。
除了下面描述的引用计数调试之外,还会执行以下额外检查:
- 额外的检查被添加到对象分配器中。
- 额外的检查被添加到解析器和编译器。
- 检查从宽类型到窄类型的向下转换是否丢失信息。
- 许多断言被添加到字典和集合实现中。 另外,设置对象获取
test_c_api()
方法。 - 输入参数的健全性检查被添加到框架创建中。
- int 的存储使用已知的无效模式进行初始化,以捕获对未初始化数字的引用。
- 低级跟踪和额外的异常检查被添加到运行时虚拟机。
- 额外的检查被添加到内存领域的实现中。
- 额外的调试被添加到线程模块中。
可能还有这里没有提到的额外检查。
定义 Py_TRACE_REFS
启用参考跟踪。 定义后,通过向每个 PyObject 添加两个额外字段来维护活动对象的循环双向链表。 也跟踪总分配。 退出时,将打印所有现有引用。 (在交互模式下,这发生在解释器运行的每个语句之后。)由 Py_DEBUG
暗示。
更多详细信息请参考 Python 源代码分发中的 Misc/SpecialBuilds.txt
。