Skip to content

Instantly share code, notes, and snippets.

@ruoyu0088
Created June 4, 2017 08:39
Show Gist options
  • Save ruoyu0088/7fa5d4d987b977ced239606ea1604590 to your computer and use it in GitHub Desktop.
Save ruoyu0088/7fa5d4d987b977ced239606ea1604590 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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