日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

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

發布時間:2024/7/23 python 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 取余_玩转Python源码(一) quot;%squot;与“%d” 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

某一天吹水的時候,吹著吹著就吹到了一下這么一個案例。

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應該差不多。

那么都有哪些操作呢?

截圖自菜鳥教程(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。

總結

以上是生活随笔為你收集整理的python 取余_玩转Python源码(一) quot;%squot;与“%d”的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。