python 取余_玩转Python源码(一) quot;%squot;与“%d”

d68657eda14b279bbad0d6e5998729b6.png

某一天吹水的时候,吹着吹着就吹到了一下这么一个案例。

import timeitdef a():"%s, %s" % (1, 2)def b():"%s, %d" % (1, 2)def c():"%d, %d" % (1, 2)t = timeit.timeit(stmt="a()", setup="from  __main__ import a", number=1000000)
t2 = timeit.timeit(stmt="b()", setup="from  __main__ import b", number=1000000)
t3 = timeit.timeit(stmt="c()", setup="from  __main__ import c", number=1000000)print "time of a", t
print "time of b", t2
print "time of c", t3# time of a 0.178924037995
# time of b 0.420981640254
# time of c 0.651199530325

哇,这有点反直觉。输入是由两个int组成的tuple。而序列化字符串的时候,指明了类型%d竟然比不指明类型直接%s来的慢??

那这必需跟Python源码的实现逻辑有关了。以此为契机,去研究一下吧。

首先看看字节码。

============ a ============5           0 LOAD_CONST               1 ('%s, %s')3 LOAD_CONST               4 ((1, 2))6 BINARY_MODULO7 POP_TOP8 LOAD_CONST               0 (None)11 RETURN_VALUE
None
============ b ============8           0 LOAD_CONST               1 ('%s, %d')3 LOAD_CONST               4 ((1, 2))6 BINARY_MODULO7 POP_TOP8 LOAD_CONST               0 (None)11 RETURN_VALUE
None
============ c ============11           0 LOAD_CONST               1 ('%d, %d')3 LOAD_CONST               4 ((1, 2))6 BINARY_MODULO7 POP_TOP8 LOAD_CONST               0 (None)11 RETURN_VALUE

三个函数的字节码都是一样的。

简要看看一些熟悉的字节码。首先两个LOAD_CONST就是把字符串和(1,2)这两个变量压进栈内带后续字节码使用。

此时的栈:

(1,2)
-----
"%s,%s"

BINARY_MODULO还不知道是什么,但看着明显就是这个字节码实现的字符串序列化工作。它会取栈内两个变量进行操作,完成后也理应会把生成的字符串压到栈顶。

此时的栈

"1,2"

由于我们没有把这个值引用下来。因此函数结束时会POP_TOP,就是把BINARY_MODULO的结果直接弹出,不再需要。然后再把None元素Load进来,当做返回值返回。

那么显然,造成速度差别的就是BINARY_MODULO这一步了。

BINARY_MODULO研究

研究Python字节码的入口必是ceval.c。我们在这里找到BINARY_MODULO的具体实现。

w = POP();
v = TOP();
if (PyString_CheckExact(v)&& (!PyString_Check(w) || PyString_CheckExact(w))) {/* fast path; string formatting, but not if the RHS is a str subclass(see issue28598) */x = PyString_Format(v, w);
} else {x = PyNumber_Remainder(v, w);
}
Py_DECREF(v);
Py_DECREF(w);
SET_TOP(x);
if (x != NULL) DISPATCH();

一行一行看。

w,v分别是获得栈顶元素。按照顺序,分别为w=(1,2),v="%s,%s"。指的注意的是,v用的是TOP(),即"%s,%s"还保留在栈顶。

下面我们来看第一个if。如果v是一个字符串,但w不是string的子类,就直接进入PyString_Format。这里我们研究的对象明显符合。好的。直接进去这个函数进行分析。顺带说一下。PyNumber_Remainder就是整数取余运算。回想我们的python代码。其实就是v%w的操作,这么一看,绝大多数人看到%符都知道这是取余操作,包括字节码的取名都是取余的意思呀!!。只不过刚好有字符串这个拼接特例而已。

PyString_Format

研究PyString_Fromat是个体力活,毕竟各种各样的字符串拼接逻辑都与此有关。接下来废话不多说,赶紧开始吧!

由于代码很长。我直接先来一发总结,这样看代码会清晰多。

之所以%d会比%s慢,是因为格式化字符串的时候,有很多选项只对%d(%x,%f同理)生效。而在Python源码里,无论你是否注明了这些选项,它都一视同仁进行一次专门对数字类型的格式化处理。由于我们经常只是直接使用%d和%s而忽略他们的一些特殊选项,导致我们的直观感受就是%d和%s应该差不多。

那么都有哪些操作呢?

5394aa168a7573dec06a240ccfa986b7.png
截图自菜鸟教程(https://www.runoob.com/python/python-strings.html)

可以这么理解,上述的功能描述里,带“数字”字眼的,都只对整数生效。我们来举个例子。

>>> "%0+5d" % 5
'+0005'
>>> "%0+5s" % 5
'    5'

看来,对于%s的情况,填充0和整数显示+这两个选项都没有用。只有宽度为5的选项生效了。

好的,了解了这个之后,上源码就舒服多了。

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{/* 省略一些声明和检查 */char *fmt, *res;fmt = PyString_AS_STRING(format);    // 格式字符串指针fmtcnt = PyString_GET_SIZE(format);  // 格式字符串的长度Py_ssize_t reslen, rescnt, fmtcnt;reslen = rescnt = fmtcnt + 100;result = PyString_FromStringAndSize((char *)NULL, reslen);/* 默认会定义一个比格式字符串长100个字节的字符串作为返回字符串 */res = PyString_AsString(result);     while (--fmtcnt >= 0) {// 这里很简单,就是对格式字符串一个一个遍历,不是%就直接添加到res中if (*fmt != '%') {if (--rescnt < 0) {rescnt = fmtcnt + 100;reslen += rescnt;if (_PyString_Resize(&result, reslen))return NULL;res = PyString_AS_STRING(result)+ reslen - rescnt;--rescnt;}*res++ = *fmt++;}else {/* 上面的逻辑都挺清晰。接下来讲述一下遇到在格式字符串里遇到%后会做的事情 *//* 省略一堆声明 */if (*fmt == '(') {/* 这里是用来换参数集合的,比如下面这种情况,不分析源码了。"%(key2)s, %(key1)s" % {"key1":1, "key2":2} -> "2,1"*/}/* 这几个格式化选项是可以连着的, 比如"%-+ #0s",检测到该项会把flag设置一下供等一下用 */while (--fmtcnt >= 0) {switch (c = *fmt++) {case '-': flags |= F_LJUST; continue;case '+': flags |= F_SIGN; continue;case ' ': flags |= F_BLANK; continue;case '#': flags |= F_ALT; continue;case '0': flags |= F_ZERO; continue;}break;}if (c == '*') {// 有星号表示会用格式化参数的第一个来作为格式化宽度值,比如"%*s"%(5,1)// 最后得到的宽度值存在width中}else if (c >= 0 && isdigit(c)) {// 如果找到整数值,表示用该值来控制宽度,比如%5s"% 1// 最后得到的宽度值存在width中}if (c == '.') {// 遇到.号,就往后检查数字,表示小数点精度。最后得到的值会存在// prec 变量当中}if (c != '%') {// 如果%后慢没有跟%,那么就取参数列表中的下一项,准备格式化v = getnextarg(args, arglen, &argidx);if (v == NULL)goto error;}switch (c) {// -------------- 接下来这一块是跟 %s,%%,%r相关的// -------------- 它们其实都是直接字符串操作,所以很快// -------------- 格式化的套路就是把格式化的内容塞到一个叫pbuf的东西// -------------- 最后再把pbuf的内存内容直接拷贝到经过一些预处理的res中case '%':pbuf = "%";len = 1;break;case 's':// %s 直接调用参数v的tp_str 或 tp_repr 或 tp_name获得字符串temp = _PyObject_Str(v);// 这里要注意一下,这一部后面没有break!!所以直接调到case 'r'中/* Fall through */case 'r':// %r 就是限制了使用tp_repr了if (c == 'r')temp = PyObject_Repr(v);if (temp == NULL)goto error;if (!PyString_Check(temp)) {PyErr_SetString(PyExc_TypeError,"%s argument has non-string str()");Py_DECREF(temp);goto error;}// 把temp值塞到pbuf中pbuf = PyString_AS_STRING(temp);len = PyString_GET_SIZE(temp);if (prec >= 0 && len > prec)len = prec;break;// -------------------- 这里开始是整数格式化操作case 'i':case 'd':case 'u':case 'o':case 'x':case 'X':if (c == 'i')c = 'd';isnumok = 0;// 拿到参数值,并转化为PyIntObject 或 PyLongObject 存到iobj中if (PyNumber_Check(v)) {PyObject *iobj=NULL;if (_PyAnyInt_Check(v)) {iobj = v;Py_INCREF(iobj);}else {iobj = PyNumber_Int(v);if (iobj==NULL) {PyErr_Clear();iobj = PyNumber_Long(v);}}if (iobj!=NULL) {if (PyInt_Check(iobj)) {isnumok = 1;pbuf = formatbuf;// 把之间检查到的所有格式化选项,比如flags和prec,// 都跟iobj一起扔进去处理,并把处理结果塞在pbuf中,// 同时返回结果的长度len = formatint(pbuf,sizeof(formatbuf),flags, prec, c, iobj);Py_DECREF(iobj);if (len < 0)goto error;sign = 1;}else if (PyLong_Check(iobj)) {int ilen;isnumok = 1;temp = _PyString_FormatLong(iobj, flags,prec, c, &pbuf, &ilen);Py_DECREF(iobj);len = ilen;if (!temp)goto error;sign = 1;}else {Py_DECREF(iobj);}}}if (!isnumok) {PyErr_Format(PyExc_TypeError,"%%%c format: a number is required, ""not %.200s", c, Py_TYPE(v)->tp_name);goto error;}if (flags & F_ZERO)fill = '0';break;/* 下面省略其他情况,比如%f和%c等,离题了。。 *//* 省略最后再做一些预处理,比如通过width和len,计算出要补多少位,用空格还是0等等*/// 直接拷贝pbuf内容到预处理好的res中Py_MEMCPY(res, pbuf, len);}return result;
}

接下来看看formatint到底干了啥。。

具体我也没细看,不过从结构上可以看到,先用输入的prec,type和flags先格式化生成一次格式字符串。得到新的fmt。然后再以这个fmt去格式化PyObject v中的内容。无论是第一步“格式化生成格式字符串”,还是第二步“格式化生成最终字符串”,都是用C的sprintf去得到的。

Py_LOCAL_INLINE(int)
formatint(char *buf, size_t buflen, int flags,int prec, int type, PyObject *v)
{/* fmt = '%#.' + `prec` + 'l' + `type`worst case length = 3 + 19 (worst len of INT_MAX on 64-bit machine)+ 1 + 1 = 24 */char fmt[64];       /* plenty big enough! */char *sign;long x;x = PyInt_AsLong(v);if (x == -1 && PyErr_Occurred()) {PyErr_Format(PyExc_TypeError, "int argument required, not %.200s",Py_TYPE(v)->tp_name);return -1;}if (x < 0 && type == 'u') {type = 'd';}if (x < 0 && (type == 'x' || type == 'X' || type == 'o'))sign = "-";elsesign = "";if (prec < 0)prec = 1;if ((flags & F_ALT) &&(type == 'x' || type == 'X')) {/* When converting under %#x or %#X, there are a number* of issues that cause pain:* - when 0 is being converted, the C standard leaves off*   the '0x' or '0X', which is inconsistent with other*   %#x/%#X conversions and inconsistent with Python's*   hex() function* - there are platforms that violate the standard and*   convert 0 with the '0x' or '0X'*   (Metrowerks, Compaq Tru64)* - there are platforms that give '0x' when converting*   under %#X, but convert 0 in accordance with the*   standard (OS/2 EMX)** We can achieve the desired consistency by inserting our* own '0x' or '0X' prefix, and substituting %x/%X in place* of %#x/%#X.** Note that this is the same approach as used in* formatint() in unicodeobject.c*/PyOS_snprintf(fmt, sizeof(fmt), "%s0%c%%.%dl%c",sign, type, prec, type);}else {PyOS_snprintf(fmt, sizeof(fmt), "%s%%%s.%dl%c",sign, (flags&F_ALT) ? "#" : "",prec, type);}/* buf = '+'/'-'/'' + '0'/'0x'/'' + '[0-9]'*max(prec, len(x in octal))* worst case buf = '-0x' + [0-9]*prec, where prec >= 11*/if (buflen <= 14 || buflen <= (size_t)3 + (size_t)prec) {PyErr_SetString(PyExc_OverflowError,"formatted integer is too long (precision too large?)");return -1;}if (sign[0])PyOS_snprintf(buf, buflen, fmt, -x);elsePyOS_snprintf(buf, buflen, fmt, x);return (int)strlen(buf);
}

通过对比,可清晰地看到。int的tp_str的基础功能,我们自己都能写得出来。而连续两次sprintf格式化操作,可复杂多了。这里通过放出PyInt_Type的tp_str指针所指向的int_to_decimal_string函数来感受一下对比。

static PyObject *
int_to_decimal_string(PyIntObject *v) {char buf[sizeof(long)*CHAR_BIT/3+6], *p, *bufend;long n = v->ob_ival;unsigned long absn;p = bufend = buf + sizeof(buf);absn = n < 0 ? 0UL - n : n;do {*--p = '0' + (char)(absn % 10);absn /= 10;} while (absn);if (n < 0)*--p = '-';return PyString_FromStringAndSize(p, bufend - p);
}

综上所述

%d比%s复杂的原因就是%d它不是我们所想象的那么简单。Python作者得考虑一连串复杂的格式化选项。只是这些参数我们平常用不到而已。

在Python编码中,建议就是,如果我们只是单纯的%d而不带任何选项,那么使用%s会好得多。

除非修改源码,当没有指定prec,flags的时候,直接走tp_str。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔刷怎么做_原来是这样:用PS笔刷做出颜料肌理效果!

题图插画 | TX灼灼“当我意识到可以利用PS的某些工具&#xff0c;设置不同的画笔之后&#xff0c;一切都变了。有些看起来很不合逻辑的组合&#xff0c;最后产生的效果却是逼真的惊人&#xff0c;再结合合适的形状就能创作出特别又新奇的东西。”——自制笔刷收入超10万美元的插…

qt 3d迷宫游戏_机械迷宫—一款机械风格的3D立体解谜独立游戏

解谜游戏一直是游戏类别中的一个大类&#xff0c;这里面各式各样的解密游戏&#xff0c;多不胜数。解密游戏又分很多种类&#xff0c;比如动作冒险新式的解密游戏&#xff0c;文字图像类型的解密游戏&#xff1b;游戏制作上有大有小&#xff0c;但是都有一个共同特点&#xff0…

Ubuntu下的Linux内核的编译及安装

推荐群&#xff1a;C/C大学技术协会&#xff1a;145655849 Linux中的模块&#xff08;Modules) Linux的module其实可以看作是内核的插件。 在Linux系统中&#xff0c;可以通过文件 cat /proc/modules xxxxxxxxxx1 1cat /proc/modules 查看相关的驱动模块。 也可以通过命令 l…

执行公式_一学就会,一吃就瘦,超简单又好执行的减肥食谱公式!

对于很多人来说&#xff0c;没有精确计算食物重量的条件&#xff0c;无法每天花费大量的时间精力去自己准备健康的食物&#xff0c;给大家一个不需要食物称&#xff0c;只要在日常饮食基础上看看自己的手掌就能通过类比来选择好食物的种类和合适比例的方法&#xff01;碳水化合…

apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了

目录回顾昨日nacos 集成Spring Cloud Alibaba 方式Nacos Spring Boot 方式Apollo 集成自研配置中心对接无配置中心对接实现源码分析兼容 Apollo 和 Nacos NoClassDefFoundErrorApollo 自动刷新问题回顾昨日上篇文章 《一时技痒&#xff0c;撸了个动态线程池&#xff0c;源码放 …

c语言 把字符串转换为变量名_如何将抓取下来的unicode字符串转换为中文

如果抓取的数据是json数据&#xff0c;那么直接将抓取的数据用json格式输出出来就行了。如下:response requests.get(url, headersself.headers).json()如果是unicode字符串&#xff0c;那么请继续往下看大家有没有遇见抓取下来的数据是unicode字符串的&#xff1f;如下图所示…

c++ 读取访问权限冲突_Linux系统利用可执行文件的Capabilities实现权限提升

一、操作目的和应用场景Capabilities机制是在Linux内核2.2之后引入的&#xff0c;原理很简单&#xff0c;就是将之前与超级用户root(UID0)关联的特权细分为不同的功能组&#xff0c;Capabilites作为线程(Linux并不真正区分进程和线程)的属性存在&#xff0c;每个功能组都可以独…

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

家装强电弱电布线图_家装水电施工标准(图文版),装修小白一眼也能看懂。...

如果把家比喻成一个人&#xff0c;房子是骨骼&#xff0c;那么水电路则相当于人体的血管和动脉&#xff0c;正因为他们的存在&#xff0c;才赋予家鲜活的生命。由此&#xff0c;水电施工自然也成为家装工程的重中之重。水电走线原则※ 水电弹线放样施工&#xff0c;使用切割机开…

0基础必看:如何轻松成为C语言高手

诞生于上世纪70年代的C语言是一门古老的语言了, 但作为一门底层语言,时至今日它仍然非常强大。学习C语言能够为学习其他更复杂的语言打下良好的基础&#xff0c;因为你在C语言中学习到的知识对几乎所有的编程语言都适用。下面就来看看如何开始用C语言编程吧。   工具   Mic…

python列表元素之和_python实现计算列表元素之和

目标&#xff1a;定义一个数字列表&#xff0c;并计算列表元素之和。 例如&#xff1a; 输入 : [12, 15, 3, 10] 输出 : 40 方法一&#xff1a;total 0 list1 [11, 5, 17, 18, 23] for ele in range(0, len(list1)): total total list1[ele] print("列表元素之和为: &…

双水泵轮换工作原理图_一用一备式冷凝水泵应急电源的设计与实现

为保障山西通州集团兴益化工有限公司10万吨甲醇工程一用一备式冷凝水泵的安全运行&#xff0c;专门设计动力负载应急电源。作者阐述了动力负载应急电源基本原理及其控制系统设计&#xff0c;经现场调试运行&#xff0c;动力负载应急电源很好满足生产现场要求。山西通州集团兴益…

基础学习——C语言递归解决分鱼问题

如有小伙伴想学习C语言基础&#xff0c;可以进群731871503进行交流学习&#xff0c;提升编程&#xff0c;共同进步 问题描述 A、B、C、D、E这5个人合伙夜间捕鱼&#xff0c;凌晨时都已经疲惫不堪&#xff0c;于是各自在河边的树丛中找地方睡着了。第二天日上三竿时&#xff0…

memkind版本查看_不同价位值得买轻薄本推荐~2020国庆篇

说起轻薄本&#xff0c;你理想中的一台优秀机型是什么样的&#xff1f;轻薄便携、质感上乘、高颜值、逼格、手感佳、续航持久、屏幕素质高、独立小键盘、性能激进……和游戏本的鲜明对比&#xff0c;注定了是两种不同的“菜”。2020年&#xff0c;随着AMD的崛起&#xff0c;部分…

main方法 如何去掉http debug日志_在MyBatis中如何使用collection标签实现嵌套查询?...

# 需求升级在上篇博客《一对多的关系&#xff0c;在MyBatis中如何映射&#xff1f;》中&#xff0c;我们实现了需求&#xff1a;根据用户id查询用户信息的同时获取用户拥有的角色。因为角色可以拥有多个权限&#xff0c;所以本篇博客我们升级需求为&#xff1a;根据用户id查询用…

movielens推荐系统_案例|推荐系统的评估指标

推荐系统能够为用户提供个性化体验&#xff0c;现在基本上各大电商平台、资讯平台都会用推荐系统为自家评价下的用户提供千人千面的服务。平均精度均值&#xff08;Mean Average Precision&#xff0c;MAP&#xff09;便是评估推荐系统性能的度量标准之一。但是&#xff0c;使用…

mysql群集配置_CentOS7 - 建立一个MySQL集群

Standing up a MySQL cluster此配方将指导您完成设置MySQL群集的过程。 通过跨多个系统划分数据并维护副本以避免单点故障&#xff0c;群集数据库可以应对可伸缩性和高可用性的挑战。集群的成员称为节点。 MySQL集群中有三种节点类型&#xff1a;数据节点&#xff0c;API节点和…

叮!您收到一份超值Java基础入门资料!

Java语言有什么特点&#xff1f;如何最大效率的学习&#xff1f;深浅拷贝到底有何区别&#xff1f;阿里巴巴高级开发工程师为大家带来Java系统解读&#xff0c;带你掌握Java技术要领&#xff0c;突破重点难点&#xff0c;入门面向对象编程&#xff0c;以详细示例带领大家Java基…

jop怎么读音英语怎么说_“春晚”英语怎么说?

大家都说近年来的春节年味越来越淡&#xff0c;每年陪老人一起看春晚、上网吐槽春晚&#xff0c;应该算是最有年味的一件事了吧&#xff01;你期待今年的春晚吗&#xff1f;在春晚即将开播之际&#xff0c;先和我一起了解一些有关“春晚”的英语知识吧&#xff01;01、“春晚”…

2019 年软件开发人员必学的编程语言 Top 3

这篇文章将探讨编程语言世界的现在和未来&#xff0c;这些语言让新一代软件开发者成为这个数字世界的关键参与者&#xff0c;他们让这个世界变得更健壮、连接更加紧密和更有意义。开发者要想在 2019 年脱颖而出&#xff0c;这三门语言一定要关注。 作为软件开发者&#xff0c;…