root@8a57bfaacaf7:/tmp/Python-3.5.3# python3 Python 3.5.3 (default, Mar 62018, 12:34:13) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> import myclass >>> help(myclass) Help on built-in module myclass:
NAME myclass - a example module for defining built-in data type
CLASSES builtins.object Empty classEmpty(builtins.object) | built-in Empty type | | Methods defined here: | | __new__(*args, **kwargs) from builtins.type | Create andreturn a new object. See help(type) for accurate signature.
Python 3.5.3 (default, Mar 62018, 13:53:21) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> from myclass import Record >>> help(Record) Help on classRecordin module myclass:
classRecord(builtins.object) | built-in Record type | | Methods defined here: | | __new__(*args, **kwargs) from builtins.type | Create andreturn a new object. See help(type) for accurate signature. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | name | name of the record | | value | value of the record (END) >>> # 我们看到 Record 类有 name 和 value 两个成员,并且都有我们写好的 Doc ... >>> r = Record() >>> r <myclass.Record object at 0x7f2127a047b0> >>> r.name # 由于我们在 member table 里面给 name 的 type 为 T_OBJECT_EX,而我们又没有初始化 name 的值,所以会异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: name >>> r.name = "Year" >>> r.value # 我们在 member table 里面给 value 的 type 为 T_OBJECT,而我们又没有初始化 value 的值,所以会返回 None >>> r.value = 2018# 由于我们给 value 的 flag 为 READONLY, 所以无法写入置它的值 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: readonly attribute >>> r.name 'Year' >>> r.name = 233333 >>> r.name 233333 >>> r.name = list() # name 的类型可以为任意 Object >>> r.name []
至此,我们基本实现了带有两个成员变量的类。
但是这个类还有些行为不是我们所期望的,比如 name 的类型应该限定为 string,而 value 的 Readonly 属性并不完全等同于 private 的属性。
2.2 定义类的 new 函数 new 函数相当于 C++ 里面的 Constructor,对应 Python 中的 __new__ 函数,我们在这一步创建并返回类的实例。new 函数和 Python 中的 __new__ 有相同的 prototype,相当于 def new(type, bases=tuple(), attributes=dict())。
定义 new 函数之后再把它插入到 tp_init slot里面去:record_type.tp_init = (initproc)record_init;
在 Interpreter 里面测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Python 3.5.3 (default, Mar 62018, 15:35:52) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> from myclass import Record >>> r = Record(name="Year", value=2018) >>> r.name 'Year' >>> r.value 2018 >>> r = Record() # 现在我们的 init 函数要求至少提供 name 参数 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Required argument 'name' (pos 1) not found >>> r = Record(name="Year") >>> r.name 'Year' >>>
嗯,现在我们已经给 Record 类添加了两个成员变量,并且定义了 new 函数 和 init 成员函数来控制类成员的创建和初始化。
接下来给我们 Record 添加一些成员函数。
2.4 定义类的成员函数
我们个 Record 类添加一个成员函数 print 来显示其 name 和 value, 添加一个 set_value 来改变 value 的值。
Python 3.5.3 (default, Mar 62018, 16:20:01) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> from myclass import Record >>> r = Record("year") >>> r.print() 'year None' >>> r.set_value(2018) >>> r.print() 'year 2018'
2.5 定义类的 property
我们的 Record 类还有两个令人不爽的地方,其一是我们希望 name 成员属性只能是 string 类型,其二是我们希望通过更简单的方式来读写 value。
这两点不爽都能通过 property 来解决。
在实现 property 之前,我们先在 init 函数中强制 parse name 为 str:
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &name, &value)) return-1;
if (name) { if (!PyUnicode_Check(name)) { PyErr_SetString(PyExc_TypeError, "The name attribute must be a string"); return-1; } tmp = self->name; Py_INCREF(name); self->name = name; Py_XDECREF(tmp); }
Python 3.5.3 (default, Mar 62018, 16:46:51) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> from myclass import Record >>> r = Record(1) # name 必须为 str Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: argument 1 must be str, notint >>> r = Record("year")
>>> r.print() 'year None' >>>
在初始化 Record 类的实例时 name 必须为 str,但是我们仍然可以之后直接通过 r.name = 2 来把它改成其它类型。这时候,property 就派上用场了。
定义 property 的过程是这样的,先定义 getter 和 setter, 再把它们放入到 getset 表里面去,然后把这张表插入到 tp_getset slot 里。
我们打算把 name 和 value 都变成 property,所以我们就不再在 member 表里面定义它们了:
Python 3.5.3 (default, Mar 62018, 17:11:08) [GCC 5.4.020160609] on linux Type"help", "copyright", "credits"or"license"for more information. >>> from myclass import Record >>> r = Record("year") >>> r.print() 'year None' >>> r.name = 2# name 必须为 str Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: The name attribute value must be a string >>> r.value = 222# 使用 property 而不是 set_value 来改变 value 的值 >>> r.print() 'year 222' >>> r.name = "YEAR" >>> r.print() 'YEAR 222' >>> del r.name # 不能删除 name 属性 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Cannot delete name >>>
至此,我们已经实现了一个拥有成员变量和函数,定义了 new 和 init,并且有 property 的 Record 类。类的基本属性全部都在我们的掌握之中。
我们将在下一章专门探索类的高级特性——类的其它特殊函数、内存管理和类的继承。