无意中发现,在python中,一个表单的操作
string1.join(string2)
可以等价地表示为
string2.replace('', string1)[len(string1):-len(string1)]
此外,在尝试timeit
了几个不同大小的输入之后,这种奇怪的加入方式似乎快了两倍多。
- 为什么join方法要慢一些?
- 像这样替换空字符串是安全/定义明确的事情吗?
无意中发现,在python中,一个表单的操作
string1.join(string2)
可以等价地表示为
string2.replace('', string1)[len(string1):-len(string1)]
此外,在尝试timeit
了几个不同大小的输入之后,这种奇怪的加入方式似乎快了两倍多。
所以首先,让我们分解一下为什么会这样。
>>> string1 = "foo"
>>> string2 = "bar"
>>> string1.join(string2)
'bfooafoor'
string1
这是在每个项目(字符)之间放置的操作string2
。
所以替换空字符串会做一些有趣的事情,它将空字符之间的间隙计算为空字符串,因此基本上完成了相同的任务,除了在开始和结束处有一个额外的分隔符:
>>> string2.replace('', string1)
'foobfooafoorfoo'
因此,切掉这些会产生与以下相同的结果str.join()
:
>>> string2.replace('', string1)[len(string1):-len(string1)]
'bfooafoor'
显然,这个解决方案的可读性远低于str.join()
,所以我总是反对它。str.join()
还被开发为在所有平台上都高效。在某些版本的 Python 上替换空字符串的效率可能要低得多(我不知道是否是这种情况,但这是有可能的——就像在 CPython 中重复连接相当快,但在其他地方不一定是这种情况。)
我什至在文档中找不到任何表明替换空字符串的行为应该以这种方式起作用的任何内容,文档str.replace()
简单地说:
返回字符串的副本,其中所有出现的子字符串 old 都替换为 new。如果给定了可选参数 count,则仅替换第一个 count 出现。
我认为我们没有理由假设字母之间的间隙应该算作空字符串的出现(可以说,您可以在字符串的任何位置放置无限的空字符串),因此,依赖这种行为可能是一个坏主意。
这种操作也非常少见——将一系列字符串连接在一起更为常见——连接字符串的单个字符并不是我个人经常需要做的事情。
有趣的是,这在Python 源代码x.replace("", y)
中似乎是特殊情况:
2347 /* Algorithms for different cases of string replacement */
2348
2349 /* len(self)>=1, from="", len(to)>=1, maxcount>=1 */
2350 Py_LOCAL(PyStringObject *)
2351 replace_interleave(PyStringObject *self,
2352 const char *to_s, Py_ssize_t to_len,
2353 Py_ssize_t maxcount)
2354 {
...
很可能是这种特殊的外壳使它表现良好。同样,由于文档中没有提到,这是一个实现细节,假设它在其他 Python 版本中也能快速(或完全)工作是错误的。
正如 Lattyware 所提到的,对于空字符串替换,它是一种特殊情况,replace_interleave
它是一个直接循环,其中来自源和来自字符串的替代字符被复制到结果字符串。循环被编码为尽可能快。
count = self_len+1;
count -= 1;
Py_MEMCPY(result_s, to_s, to_len);
result_s += to_len;
for (i=0; i<count; i++) {
*result_s++ = *self_s++;
Py_MEMCPY(result_s, to_s, to_len);
result_s += to_len;
}
/* Copy the rest of the original string */
Py_MEMCPY(result_s, self_s, self_len-i);
Join方法也有一个Loop,但是也有改进的地方(通过我还没有找到所有方面的原因都已经按照下面的方式编码了)和瓶颈的原因。
char *sep = PyString_AS_STRING(self);
seq = PySequence_Fast(orig, "");
/* Catenate everything. */
p = PyString_AS_STRING(res);
for (i = 0; i < seqlen; ++i) {
size_t n;
item = PySequence_Fast_GET_ITEM(seq, i);
n = PyString_GET_SIZE(item);
Py_MEMCPY(p, PyString_AS_STRING(item), n);
p += n;
if (i < seqlen - 1) {
Py_MEMCPY(p, sep, seplen);
p += seplen;
}
}
正如您在这里看到的,在循环内
上述三个操作,即使它可能是内联的,也有相当大的开销。注意这也解释了为什么使用 List 与使用 STring 相比有不同的结果,正如 Blended 所观察到的那样
还比较两个循环,
前者
最后说明
编写时牢记所有形式的str.join
可迭代和序列,而不仅仅是字符串,并且没有详细说明,它完全可以预期通用例程的执行速度可能不如专用例程来服务特定形式的数据。