Created
June 4, 2017 08:39
-
-
Save ruoyu0088/7fa5d4d987b977ced239606ea1604590 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from utils import set_cffi_tmpdir, disable_stop_on_error\n", | |
"set_cffi_tmpdir()\n", | |
"disable_stop_on_error()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# 让类型对象支持排序" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Python 3中类型不能排序,下面的代码抛出`TypeError`异常:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"ename": "TypeError", | |
"evalue": "unorderable types: type() < type()", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", | |
"\u001b[1;32m<ipython-input-2-d0fcb0aa548d>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[0mclasses\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mZ\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mB\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mA\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mC\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 14\u001b[1;33m \u001b[0msorted\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclasses\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m: unorderable types: type() < type()" | |
] | |
} | |
], | |
"source": [ | |
"class A:\n", | |
" pass\n", | |
"\n", | |
"class B:\n", | |
" pass\n", | |
"\n", | |
"class C:\n", | |
" pass\n", | |
"\n", | |
"class Z:\n", | |
" pass\n", | |
"\n", | |
"classes = [Z, B, A, C]\n", | |
"sorted(classes)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"我们知道对象的比较行为由其类型中的魔法方法`__lt__()`决定。而在`type`对象的字典中没有`__lt__`:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"False" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"\"__lt__\" in type.__dict__" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"下面我们定义`__lt__()`函数并将其添加进`type.__dict__`,但是抛出异常。这是由于Python对类型对象的字典进行了写保护:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"ename": "TypeError", | |
"evalue": "'mappingproxy' object does not support item assignment", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", | |
"\u001b[1;32m<ipython-input-10-356183d7f355>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__name__\u001b[0m \u001b[1;33m<\u001b[0m \u001b[0mother\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mtype\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__dict__\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"__lt__\"\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m__lt__\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m: 'mappingproxy' object does not support item assignment" | |
] | |
} | |
], | |
"source": [ | |
"def __lt__(self, other):\n", | |
" return self.__name__ < other.__name__\n", | |
"\n", | |
"type.__dict__[\"__lt__\"] = __lt__" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"`type.__dict__`是一个`mappingproxy`对象,它对字典对象进行只读包装:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"mappingproxy" | |
] | |
}, | |
"execution_count": 16, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"type(type.__dict__)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"为了往`type`的字典中添加内容,必须获得该字典的引用。可以使用`gc.get_referents()`实现。`get_referents(obj)`可以获得`obj`对象引用的所有其它对象。因此可以使用它获取`mappingproxy`对象内部包装的字典对象。下面将`__lt__()`函数添加进行`type`的字典中:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 17, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import gc\n", | |
"type_dict = gc.get_referents(type.__dict__)[0]\n", | |
"type_dict[\"__lt__\"] = __lt__\n", | |
"\"__lt__\" in type.__dict__" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"然而即使在字典中添加了`__lt__`函数,仍然无法对类型对象进行排序。" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"ename": "TypeError", | |
"evalue": "unorderable types: type() < type()", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", | |
"\u001b[1;32m<ipython-input-18-563726727430>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0msorted\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclasses\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[1;31mTypeError\u001b[0m: unorderable types: type() < type()" | |
] | |
} | |
], | |
"source": [ | |
"sorted(classes)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"为了让类型对象支持比较,还需要修改`type`对象对应的C结构体`PyTypeObject`的`tp_richcompare`字段。" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 51, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
" 4 0 LOAD_FAST 0 (a)\n", | |
" 3 LOAD_FAST 1 (b)\n", | |
" 6 COMPARE_OP 0 (<)\n", | |
" 9 RETURN_VALUE\n" | |
] | |
} | |
], | |
"source": [ | |
"import dis\n", | |
"\n", | |
"def f(a, b):\n", | |
" return a < b\n", | |
"\n", | |
"dis.dis(f)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"一个自定义的类要支持比较,必须满足以下两个条件:\n", | |
"\n", | |
"1. 类的`__dict__`中包含名为`__lt__`的函数。\n", | |
"2. 类对应的结构体的字段`tp_richcompare`保存`slot_tp_richcompare()`函数的地址,该函数在`typeobject.c`文件中定义。\n", | |
"\n", | |
"前面我们已经在类的字典中添加了名为`__lt__`的函数,下面看看如何修改`tp_richcompare`字段。首先需要获取该字段的偏移地址:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 52, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"200" | |
] | |
}, | |
"execution_count": 52, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import cffi\n", | |
"\n", | |
"ffi = cffi.FFI()\n", | |
"ffi.cdef(\"\"\"\n", | |
"ssize_t tp_richcompare;\n", | |
"\"\"\")\n", | |
"\n", | |
"lib = ffi.verify(\"\"\"\n", | |
"ssize_t tp_richcompare = offsetof(PyTypeObject, tp_richcompare);\n", | |
"\"\"\")\n", | |
"\n", | |
"lib.tp_richcompare" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"由于`slot_tp_richcompare()`为`static`函数,无法直接获取其地址。因此这里定义一个`Dummy`类,其`tp_richcompare`字段中保存的就是`slot_tp_richcompare()`的地址:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 53, | |
"metadata": { | |
"collapsed": true | |
}, | |
"outputs": [], | |
"source": [ | |
"class Dummy:\n", | |
" def __lt__(self, other):\n", | |
" return id(self) < id(other)\n", | |
" \n", | |
"class Dummy2(Dummy):\n", | |
" pass\n", | |
"\n", | |
"class Dummy3:\n", | |
" def __lt__(self, other):\n", | |
" return id(self) < id(other)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"下面获取函数地址,并与`Dummy2`和`Dummy3`的字段中的值比较,它们都是同一个地址:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 54, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"addr_slot_tp_richcompare = int(ffi.cast(\"ssize_t *\", id(Dummy) + lib.tp_richcompare)[0])\n", | |
"\n", | |
"assert addr_slot_tp_richcompare == int(ffi.cast(\"ssize_t *\", id(Dummy2) + lib.tp_richcompare)[0])\n", | |
"assert addr_slot_tp_richcompare == int(ffi.cast(\"ssize_t *\", id(Dummy3) + lib.tp_richcompare)[0])" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"下面将`addr_slot_tp_richcompare`写入到`type`对应的结构体的`tp_richcompare`字段中:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 55, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"ffi.cast(\"ssize_t *\", id(type) + lib.tp_richcompare)[0] = addr_slot_tp_richcompare" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"于是就可以对类进行排序了:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 56, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[__main__.A, __main__.B, __main__.C, __main__.Z]" | |
] | |
}, | |
"execution_count": 56, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sorted(classes)" | |
] | |
} | |
], | |
"metadata": { | |
"anaconda-cloud": {}, | |
"kernelspec": { | |
"display_name": "Python [default]", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.5.2" | |
}, | |
"toc": { | |
"toc_cell": false, | |
"toc_number_sections": true, | |
"toc_threshold": 4, | |
"toc_window_display": false | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 1 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment