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 字符串的机制 和 原理相信你有更高更深层的认识!


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


欢迎投稿: [email protected]
[图片上传失败...(image-b76e8c-1549099935130)]


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

你可能感兴趣的:(Python源码剖析-PyStringObject对象和STR(下))