Python rstrip()方法你不知道的“坑”

784 阅读2分钟

rstrip()方法作用

删除字符串末尾的指定字符,默认空白符,包括空格、换行符、回车符、制表符

s = "hello\n  \t  \r "
s.rstrip() # helloa = "python.py"
a.rstrip('.py') # python

看着用法很简单,直到有一天偶然发现这样一段代码

a = "pythony.py"
a.rstrip('.py') # python

按照我之前的理解,应该输出pythony,为啥是python呢?赶紧看官方文档

str.rstrip([chars]) 返回原字符串的副本,移除其中的末尾字符。 chars 参数为指定要移除字符的字符串。 如果省略或为 None,则 chars 参数默认移除空白符。 实际上 chars 参数并非指定单个后缀;而是会移除参数值的所有组合:

似乎明白了一些,想要理解的更透彻,就得翻翻源码了。

#define LEFTSTRIP 0
#define RIGHTSTRIP 1
#define BOTHSTRIP 2static PyObject *
unicode_rstrip_impl(PyObject *self, PyObject *chars)
/*[clinic end generated code: output=4a59230017cc3b7a input=62566c627916557f]*/
{
    return do_argstrip(self, RIGHTSTRIP, chars);
}
​
static PyObject *
do_argstrip(PyObject *self, int striptype, PyObject *sep)
{
    if (sep != Py_None) {
        if (PyUnicode_Check(sep))
            return _PyUnicode_XStrip(self, striptype, sep);
        else {
            PyErr_Format(PyExc_TypeError,
                         "%s arg must be None or str",
                         STRIPNAME(striptype));
            return NULL;
        }
    }
​
    return do_strip(self, striptype);
}

可以看到,如果指定字符不为空,会调用PyUnicode_Check函数检查字符串类型,如果是则调用_PyUnicode_XStrip,否则抛出异常。

a = "pythony.py"
a.rstrip(1) # TypeError: rstrip arg must be None or str

我们看看_PyUnicode_XStrip函数

PyObject *
_PyUnicode_XStrip(PyObject *self, int striptype, PyObject *sepobj)
{
    const void *data;
    int kind;
    Py_ssize_t i, j, len;
    BLOOM_MASK sepmask;
    Py_ssize_t seplen;
​
    if (PyUnicode_READY(self) == -1 || PyUnicode_READY(sepobj) == -1)
        return NULL;
​
    kind = PyUnicode_KIND(self);
    data = PyUnicode_DATA(self);
    len = PyUnicode_GET_LENGTH(self);
    PyObject_Print(PyLong_FromSsize_t(len), stdout, 0); // 调用api输出,自己调试加的
    seplen = PyUnicode_GET_LENGTH(sepobj);
    sepmask = make_bloom_mask(PyUnicode_KIND(sepobj),
                              PyUnicode_DATA(sepobj),
                              seplen);
​
    i = 0;
    if (striptype != RIGHTSTRIP) {
        while (i < len) {
            Py_UCS4 ch = PyUnicode_READ(kind, data, i);
            if (!BLOOM(sepmask, ch))
                break;
            if (PyUnicode_FindChar(sepobj, ch, 0, seplen, 1) < 0)
                break;
            i++;
        }
    }
​
    j = len;
    if (striptype != LEFTSTRIP) {
        j--;
        while (j >= i) {
            Py_UCS4 ch = PyUnicode_READ(kind, data, j);
            if (!BLOOM(sepmask, ch))
                break;
            if (PyUnicode_FindChar(sepobj, ch, 0, seplen, 1) < 0)
                break;
            j--;
        }
​
        j++;
    }
​
    return PyUnicode_Substring(self, i, j);
}

解释一下这段代码

  1. 首先检查输入的self和sepobj是否已经准备好。如果任意一个对象没有准备好,则返回NULL
  2. 获取self对象的数据和长度,以及sepobj对象的数据和长度。同时,生成一个Bloom filter的掩码,用于快速判断一个字符是否属于sepobj中的字符集
  3. 根据striptype的值,决定是去除左边、右边还是两边的字符。如果是去除左边的字符,那么从字符串的左端开始扫描,找到第一个不属于sepobj的字符的位置i,然后返回从i到字符串结尾的子串。如果是去除右边的字符,那么从字符串的右端开始扫描,找到第一个不属于sepobj的字符的位置j,然后返回从字符串开头到j的子串。如果是去除两边的字符,那么先执行左边的去除操作,然后再在结果的基础上执行右边的去除操作。
  4. 返回去除前缀和/或后缀后的新字符串对象
  5. 这段代码使用了Bloom filter来加速字符集的匹配操作,从而提高了函数的执行效率。Bloom filter是一种基于哈希的数据结构,可以用来判断一个元素是否属于一个集合,具有快速、高效的特点。在这段代码中,我们将sepobj中的所有字符都插入到了一个Bloom filter中,然后在扫描字符串时,只需要通过Bloom filter来快速判断一个字符是否属于sepobj中的字符集,从而避免了多次调用PyUnicode_FindChar函数的开销。这种优化技巧在处理大量数据时非常有效,可以大大提高程序的性能。

结合案例

a = "pythony.py"
a.rstrip('.py')

j = 9, ch = 'y',依次判断ch是否在'.py'中可以找到,j = 5,ch = 'n', 此时在'.py'中未找到,j++变为6,最终返回,类似切片a[0:6],所以输出'python'

到目前为止,我们已经了解了rstrip()底层的运行逻辑,那我们就想要删除指定字符串,像上面的案例,只需删除'.py',最终输出'pythony'呢?

removesuffix()

对,这个方法就可以解决上面的问题。不过这是python3.9版本的新功能

官方介绍

str.removesuffix(suffix)

如果字符串以 suffix 字符串结尾,并且 suffix 非空,返回 string[:-len(suffix)]。 否则,返回原始字符串的副本

看看源码

static PyObject *
unicode_removesuffix_impl(PyObject *self, PyObject *suffix)
/*[clinic end generated code: output=d36629e227636822 input=12cc32561e769be4]*/
{
    int match = tailmatch(self, suffix, 0, PY_SSIZE_T_MAX, +1);
    if (match == -1) {
        return NULL;
    }
    if (match) {
        return PyUnicode_Substring(self, 0, PyUnicode_GET_LENGTH(self)
                                            - PyUnicode_GET_LENGTH(suffix));
    }
    return unicode_result_unchanged(self);
}

就是一个字符串的截取操作

欢迎关注公众号“郝同学的测开笔记”