让list打印时顺便输出索引
首先,作为基本素质,请先备份源码免得搞坏了又要下载.其次,方便自己,把玩过的东西改回去,免得自己把自己绕着了(比如应该是True的你给我打印一个False我可记不清楚呢)
定位到static PyObject *list_repr(PyListObject *v) 函数(记住了,_repr后缀应该就是负责打印的,以后都对这种函数下手就完了) 位于Objects/listobject.c:361 ,贴出来函数体吧
Py_ssize_t i;
PyObject *s;
_PyUnicodeWriter writer;
if (Py_SIZE(v) == 0) {
return PyUnicode_FromString("[]");
}
i = Py_ReprEnter((PyObject*)v);
if (i != 0) {
return i > 0 ? PyUnicode_FromString("[...]") : NULL;
}
_PyUnicodeWriter_Init(&writer);
writer.overallocate = 1;
writer.min_length = 1 + 1 + (2 + 1) * (Py_SIZE(v) - 1) + 1;
if (_PyUnicodeWriter_WriteChar(&writer, '[') < 0)
goto error;
for (i = 0; i < Py_SIZE(v); ++i) {
if (i > 0) {
if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0)
goto error;
}
s = PyObject_Repr(v->ob_item[i]);
if (s == NULL)
goto error;
if (_PyUnicodeWriter_WriteStr(&writer, s) < 0) {
Py_DECREF(s);
goto error;
}
Py_DECREF(s);
}
writer.overallocate = 0;
if (_PyUnicodeWriter_WriteChar(&writer, ']') < 0)
goto error;
Py_ReprLeave((PyObject *)v);
return _PyUnicodeWriter_Finish(&writer);
error:
_PyUnicodeWriter_Dealloc(&writer);
Py_ReprLeave((PyObject *)v);
return NULL;
然后勉强能阅读这些代码,就是先输出一个"[",然后根据情况遍历整个列表,分别调用其repr函数(这个有些特殊,内部机制实际上是函数指针),然后循环下一个,直到最后一个就输出一个"]".特别好理解.下面来搞事情. 我们先来试试输出值和它的索引,热热身. 在for外部声明一个局部变量.char *tmp_s = malloc(100); ,并分配空间; 然后在每次循环的时候格式化一下索引并修改输出函数的参数,让他打印tmp_s; 最后一个对象,特殊处理就完啦~ 代码如下:Objects/listobject.c:385-413
char *tmp_s = (char*)malloc(100);
for (i = 0; i < Py_SIZE(v); ++i) {
sprintf(tmp_s, " : %d, ", i);
if (i > 0) {
if (_PyUnicodeWriter_WriteASCIIString(&writer, tmp_s, strlen(tmp_s)))
goto error;
}
s = PyObject_Repr(v->ob_item[i]);
if (s == NULL)
goto error;
if (_PyUnicodeWriter_WriteStr(&writer, s) < 0) {
Py_DECREF(s);
goto error;
}
Py_DECREF(s);
}
writer.overallocate = 0;
sprintf(tmp_s, " : %d]", i);
if (_PyUnicodeWriter_WriteASCIIString(&writer, tmp_s, strlen(tmp_s)) < 0)
goto error;
运行效果: 实现了预期效果,但…这个索引从1开始很离谱,我们修改一下: 把L389和L411的sprintf第三个参数改成i-1 . 好,既然如此,那我们给他搞个大事情:全给我倒着输出! (偷偷删掉刚刚的恶作剧) 实现原理:修改v->ob_item 的索引,改成"数组大小-前索引-1",就是反着遍历啦 运行,正常: 突发奇想,如果嵌套列表会怎么样?肯定都反过来,下图为证:
改变dict键值位置
折腾玩list,怎么能放过dict?! 定位static PyObject *dict_repr(PyDictObject *mp) @Objects/dictobject.c:2093 (足够后面…幸好有搜索功能)
简单起见哈,我们仅仅改变key和value的值(这足够过分了,当你的朋友看见不可哈希对象(比如list)出现在键的位置,咱可以想象一下那表情[])
首先找到L2123循环开始,其他的可以简单浏览下,都能看懂的相信.仅仅讲一下很重要的两个宏(每个源码解析书都会说到,我自然不能落后对不对)Py_INCREF 和Py_DECREF
跟着vs的访问,找到这个宏一步步展开的最终结果:(以INCREF为例) object.h:475 ->object.h:461 (函数),手动找到实现,结果无能为力啊…那就直接说吧 分词:INC(increase) REF(reference) 增加引用(计数) 众所周知每个PyObject结构体都有个引用计数,这根gc(垃圾回收)紧密相连,python虚拟机的垃圾回收机制可以肤浅的理解成,维护每个对象的引用计数器,每次引用变量就自增1,当那个引用的变量被del(析构,或删除)后,自减1,gc要做的事就是在引用计数变成0的时候发现,并干掉这个没用的垃圾
当然,这么复杂的解释器内部肯定不只说的这么简单,它需要考虑是事情更多,优化也很重要,勉强这么理解就行了.
下面正式向dict进攻: 熟练地恶作剧技巧告诉我们,这个repr字眼肯定是打印用的,事实也正是如此,我深入看过它的实现,特别复杂,函数调用有接近十层(真没吹)
这里也顺便引出阅读大型项目源码的一大难点:他为了全局上的方便,牺牲局部的可读性.什么意思呢?就是说,你一个不是它项目组的人去看,多半看不大懂,因为你脑子里没有开发者脑子里的结构图,你不知道哪个函数是哪个逻辑层(我喜欢这么称呼),也不清楚它会调用到哪里去,或者被谁调用.源码阅读者若不是修养特别深,多半脑海里都仅仅是一个个散着的函数(本人自己写过中型项目,我可以清晰说出我项目里的层次而你看半天估计也看不明白),因此这里我就把关键的调用节点都给大家调查好,毕竟vs操作不那么方便(或者我不熟练,我喜欢vscode)
回归正题,为了实现目标(改变键值位置),关注到英文单词value 和key 出现的位置,如截图,在L2137和L2148位置,试着调换他们应该就行.如下: (还是那句话,记得改回来哦) 单凭那个[123, 234]:'abc' 就足够吓懂python的朋友一跳了哈哈哈 (还有那个集合作为键,真的特别反人类),ok,dict的倒腾成功!
关于如何搭建环境,请看上一章 玩腻了输出的把戏,下一章我们来盘一下运行时~
|