Python源码剖析-PyStringObject对象和STR(下)

引言

我们知道Python str 对象的神奇魅力,它使得Pythoner 初学者更加容易上手,你可以快速的对 字符串 切片, 相加,相乘, 它本身还带大量的函数支持快速的变化字符串.
[图片上传失败...(image-21c01c-1549099935130)]
例如:
[图片上传失败...(image-20143f-1549099935130)]

Str 对象方法

static PyMethodDef
string_methods[] = {
    /* Counterparts of the obsolete stropmodule functions; except
       string.maketrans(). */
    {"join", (PyCFunction)string_join, METH_O, join__doc__},
    {"split", (PyCFunction)string_split, METH_VARARGS, split__doc__},
    {"rsplit", (PyCFunction)string_rsplit, METH_VARARGS, rsplit__doc__},
    {"lower", (PyCFunction)string_lower, METH_NOARGS, lower__doc__},
    {"upper", (PyCFunction)string_upper, METH_NOARGS, upper__doc__},
    {"islower", (PyCFunction)string_islower, METH_NOARGS, islower__doc__},
    {"isupper", (PyCFunction)string_isupper, METH_NOARGS, isupper__doc__},
    {"isspace", (PyCFunction)string_isspace, METH_NOARGS, isspace__doc__},
    {"isdigit", (PyCFunction)string_isdigit, METH_NOARGS, isdigit__doc__},
    {"istitle", (PyCFunction)string_istitle, METH_NOARGS, istitle__doc__},
    {"isalpha", (PyCFunction)string_isalpha, METH_NOARGS, isalpha__doc__},
    {"isalnum", (PyCFunction)string_isalnum, METH_NOARGS, isalnum__doc__},
    {"capitalize", (PyCFunction)string_capitalize, METH_NOARGS,
     capitalize__doc__},
    {"count", (PyCFunction)string_count, METH_VARARGS, count__doc__},
    {"endswith", (PyCFunction)string_endswith, METH_VARARGS,
     endswith__doc__},
    {"partition", (PyCFunction)string_partition, METH_O, partition__doc__},
    {"find", (PyCFunction)string_find, METH_VARARGS, find__doc__},
    {"index", (PyCFunction)string_index, METH_VARARGS, index__doc__},
    {"lstrip", (PyCFunction)string_lstrip, METH_VARARGS, lstrip__doc__},
    {"replace", (PyCFunction)string_replace, METH_VARARGS, replace__doc__},
    {"rfind", (PyCFunction)string_rfind, METH_VARARGS, rfind__doc__},
    {"rindex", (PyCFunction)string_rindex, METH_VARARGS, rindex__doc__},
    {"rstrip", (PyCFunction)string_rstrip, METH_VARARGS, rstrip__doc__},
    {"rpartition", (PyCFunction)string_rpartition, METH_O,
    ....
    {NULL,     NULL}                         /* sentinel */
};

大差不差,上述中的函数名称都在 dir(str)中,这里简单说明一下这个数据结构的格式:
{"lower", (PyCFunction)string_lower, METH_NOARGS, lower__doc__}

  • lower 是对外的函数名称,可以理解为对string_lower的包装
  • string_lower 是 lower 名称相对源码的映射
  • METH_NOARGS 表示该函数没有参数,不会进行PyargparseTuple的参数解析,具体参数可查阅官网doc,后期我们将使用原生C来编写Python的插件
  • lower.__doc__ 函数声明(这是Python独特的部分,通过访问对象的 _doc_可以看到申明)
    >>> 'xx'.lower().__doc__
    "str(object='') -> string\n\nReturn a nice string representation of the object.
    nIf the argument is a string, the return value is the same object."

我们选择两个函数来进行源码的分析:


    1. swapcase
      {"swapcase", (PyCFunction)string_swapcase, METH_NOARGS, swapcase__doc__},
static PyObject *
string_swapcase(PyStringObject *self)
{
    char *s = PyString_AS_STRING(self), *s_new; //字符对象
    Py_ssize_t i, n = PyString_GET_SIZE(self);
    PyObject *newobj;

    newobj = PyString_FromStringAndSize(NULL, n);
    if (newobj == NULL)
        return NULL;
    s_new = PyString_AsString(newobj);//字符对象转化为 PyStringObject 对象,并获取该对象的ob_sval
    for (i = 0; i < n; i++) {//遍历字符串每一位(转化为大写)
        int c = Py_CHARMASK(*s++);
        if (islower(c)) {//如果小写转化为大写
            *s_new = toupper(c);   
            //#define _toupper(_Char)    ( (_Char)-'a'+'A' )
        }
        else if (isupper(c)) { //如果大写转化为小写
            *s_new = tolower(c);
        }
        else
            *s_new = c;
        s_new++;
    }
    return newobj;
}

可知上述返回的是对象的大小写对换的副本。
'xXXx'.swapcase() -> XxxX

    1. replace
      {"replace", (PyCFunction)string_replace, METH_VARARGS, replace__doc__}
//__doc__声明,通过 str.replace.__doc__ 访问
PyDoc_STRVAR(replace__doc__,
"S.replace(old, new[, count]) -> string\n\
\n\
Return a copy of string S with all occurrences of substring\n\
old replaced by new.  If the optional argument count is\n\
given, only the first count occurrences are replaced.");

//函数主体
static PyObject *
string_replace(PyStringObject *self, PyObject *args)
{
    Py_ssize_t count = -1;
    PyObject *from, *to;
    const char *from_s, *to_s;
    Py_ssize_t from_len, to_len;
    //一系列检查和转化
    //如果为unicode,则 return PyUnicode_Replace,这里不做深入探究。
    // 是否为str 或者 unicode
    PyObject_AsCharBuffer(from, &from_s, &from_len);
    PyObject_AsCharBuffer(to, &to_s, &to_len);
    return (PyObject *)replace((PyStringObject *) self,from_s, from_len,to_s, to_len, count);
    //最后的replace通过判断 from_len, to_len的长度分别进行不同的操作,比如 from_len =0 :
/* insert the 'to' string everywhere.   */
/*    >>> "Python".replace("", ".")     */
/*    '.P.y.t.h.o.n.'                   */
}

>>> 'AAAAX'.replace('A','x',2) -> 'xxAAX'
>>> "".replace("", "A") == "A"`

源码部分比较多,涉及大量的条件判断,我们可以知道,一个简单的字符串是由大量的原生函数帮助实现的,正所谓前人栽树,后人乘凉,你说Python简单,那是因为你们有看到简单背后的强大,正如你看到中国国家如此和平,没看到边疆保卫家园的战士默默的付出!
介绍完str 对象的函数, 我们再来看看为何str对象支持 序列操作?如:
assert 'x' in 'xXXX'


string_as_sequence

会到 PyString_Type
我们可以发现:
&string_as_sequence, /* tp_as_sequence */
这就是 str 对象支持 诸如 sequence 那样灵活能力的原因,其本质是 PyType_Type 结构体的该位置会用来实例化对象 tp_new的时候进行检查操作, 由于内部大量的宏 (define) ,在实例化 PyStringObject 的时候会 将该对象 的“属性” 信息取址 到 &PyString_Type , 因此 PyStringObject 与 string_as_sequence 函数产生了关联。

我们来看看string_as_sequence 结构:

static PySequenceMethods string_as_sequence = {
    (lenfunc)string_length, /*sq_length*/
    (binaryfunc)string_concat, /*sq_concat*/
    (ssizeargfunc)string_repeat, /*sq_repeat*/
    (ssizeargfunc)string_item, /*sq_item*/
    (ssizessizeargfunc)string_slice, /*sq_slice*/
    0,                  /*sq_ass_item*/
    0,                  /*sq_ass_slice*/
    (objobjproc)string_contains /*sq_contains*/
};

从上我们可以看到,或者更通俗的解释:

在Python 虚拟机遇到 in 操作字节码的时候, 当判断到 in 的右边为 PyStringObject 对象的时候, 会调用 PyString_Type -> tp_as_sequence 也就是&string_as_sequence -> string_contains 方法。

为了一探究竟,这里就插入一个题外话:

in 字节码探索

[图片上传失败...(image-c334bb-1549099935130)]
关查可以 in 字节码 对应的就是 COMPARE_OP (#define COMPARE_OP 107 /* Comparison operator */ 也就是 十进制107,0x6b)

在 ceval.c 中:

        TARGET(COMPARE_OP)
        {
            w = POP(); //栈弹出操作 in 左边
            v = TOP(); // in 右边的值
            if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) { //当为正数的时候
                /* INLINE: cmp(int, int) */
                register long a, b;
                register int res;
                a = PyInt_AS_LONG(v);
                b = PyInt_AS_LONG(w);
                switch (oparg) {
                case PyCmp_LT: res = a <  b; break;
                case PyCmp_LE: res = a <= b; break;
                ....
                default: goto slow_compare; //显然是走进default分支
                }
                x = res ? Py_True : Py_False;
                Py_INCREF(x);
            }
            else {
              slow_compare: 
                x = cmp_outcome(oparg, v, w);
            }

很显然, 从模拟栈中弹出的是 PyStringObject 对象, 所以会进入 cmp_outcome(oparg,v,w)

static PyObject *
cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
    int res = 0;
    switch (op) {
    case PyCmp_IS:    // is 操作符
        res = (v == w);
        break;
    case PyCmp_IS_NOT:   // is not 操作符
        res = (v != w);
        break;
    case PyCmp_IN:       // in 操作符
        res = PySequence_Contains(w, v);
        if (res < 0)
            return NULL;
        break;
    case PyCmp_NOT_IN:    // not in 操作符
        res = PySequence_Contains(w, v);
        if (res < 0)
            return NULL;
        res = !res;
        break;
    case PyCmp_EXC_MATCH:
            ......

继续跟进 PySequence_Contains(w, v)
【abstract.c】

/* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
 * Use sq_contains if possible, else defer to _PySequence_IterSearch().
 */
int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
    Py_ssize_t result;
    if (PyType_HasFeature(seq->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
        PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
        if (sqm != NULL && sqm->sq_contains != NULL)
            return (*sqm->sq_contains)(seq, ob); //猜测完全一致 , 调用的就是
    }
}

seq->ob_type->tp_as_sequence->sq_contains
至此,验证确实是这么一套机制。

继续回到 PyString_Type 序列操作时 调用的 string_contains 方法:
其是对stringlib_contains_obj 方法的包装, 它又调用了stringlib_find

Py_LOCAL_INLINE(Py_ssize_t)
stringlib_find(const STRINGLIB_CHAR* str, Py_ssize_t str_len,
               const STRINGLIB_CHAR* sub, Py_ssize_t sub_len,
               Py_ssize_t offset)
{
    Py_ssize_t pos;

    if (str_len < 0)
        return -1;
    if (sub_len == 0)
        return offset;

    pos = fastsearch(str, str_len, sub, sub_len, -1, FAST_SEARCH);  // contains 操作的最终调用

    if (pos >= 0)
        pos += offset;

    return pos;
}

最终判断 'xxx' in 'yyy' 的重任就交给了 fastsearch,其实就是C中的字符串查找的动作,但是80%的代码都是在进行类型检查操作。
如果找到则 pos >0。 否则 pos <0

总结

高级语言层面的 Python 字符串你是否已经完全掌握了呢? 经过本章的细化, Python 字符串的机制 和 原理相信你有更高更深层的认识!


@ 敬贤。 源码就是砖头, 高级语言只能算是成品。


欢迎投稿: 787518771@qq.com
[图片上传失败...(image-b76e8c-1549099935130)]


2018-06-23 14:05:05 星期六

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,666评论 0 5
  • Python史上最全开发总结(转自静熙老师哈哈哈) 两本不错的书: 《Python参考手册》:对Python各个标...
    春风在抱阅读 752评论 1 4
  • 此时此刻,脑子很乱。但又不知道从何说起。 负面情绪爆表,所谓的反抗,不过是别人无数次提到对你来说敏感的点之后所采取...
    HCT116阅读 294评论 0 0
  • 初七是我姥姥的八十岁大寿,这天无比地热闹,亲朋好友都来为姥姥祝寿,但我发现姥姥的背又变驼了些,记忆中的姥姥就一直驼...
    青橙梓阅读 342评论 0 4
  • 我是东海的一条人鱼,对,就是你们说的美人鱼。那个和安徒生童话同样种族的人鱼。千百年来我们一直生活在东海的深深海底,...
    小九飞飞飞阅读 697评论 7 4