大多数扩展类型只使用 简单 属性,那么,是什么让属性变得“简单”呢?只需要满足下面几个条件:
当调用 PyType_Ready() 时,必须知道属性的名称。
不需要特殊的处理来记录属性是否被查找或设置,也不需要根据值采取操作。
请注意,此列表不对属性的值、值的计算时间或相关数据的存储方式施加任何限制。
当 PyType_Ready() 被调用时,它会使用由类型对象所引用的三个表来创建要放置到类型对象的字典中的 descriptor。 每个描述器控制对实例对象的一个属性的访问。 每个表都是可选的;如果三个表全都为 NULL,则该类型的实例将只有从它们的基础类型继承来的属性,并且还应当让 tp_getattro 和 tp_setattro 字段保持为 NULL,以允许由基础类型处理这些属性。
表被声明为object::类型的三个字段:
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
如果 tp_methods 不为 NULL,则它必须指向一个由 PyMethodDef 结构体组成的数组。 表中的每个条目都是该结构体的一个实例:
typedef struct PyMethodDef {
const char *ml_name; /* method name */
PyCFunction ml_meth; /* implementation function */
int ml_flags; /* flags */
const char *ml_doc; /* docstring */
} PyMethodDef;
应当为该类型所提供的每个方法都应定义一个条目;从基类型继承来的方法无需定义条目。 还需要在末尾加一个额外的条目;它是一个标记数组结束的哨兵条目。 该哨兵条目的 ml_name 字段必须为 NULL。
第二个表被用来定义要直接映射到实例中的数据的属性。 各种原始 C 类型均受到支持,并且访问方式可以为只读或读写。 表中的结构体被定义为:
typedef struct PyMemberDef {
const char *name;
int type;
int offset;
int flags;
const char *doc;
} PyMemberDef;
对于表中的每个条目,都将构建一个 descriptor 并添加到类型中使其能够从实例结构体中提取值。 type 字段应包含一个类型代码如 Py_T_INT 或 Py_T_DOUBLE;该值将用于确定如何将 Python 值转换为 C 值或反之。 flags 字段用于保存控制属性要如何被访问的旗标:你可以将其设为 Py_READONLY 以防止 Python 代码设置它。
使用 tp_members 表来构建用于运行时的描述器还有一个有趣的优点是任何以这种方式定义的属性都可以简单地通过在表中提供文本来设置一个相关联的文档字符串。 一个应用程序可以使用自省 API 从类对象获取描述器,并使用其 __doc__ 属性来获取文档字符串。
与 tp_methods 表一样,需要有一个值为 NULL 的 ml_name 哨兵条目。 |