在第一次出现分隔符时分割字符串的最佳方法是什么?

例如:

"123mango abcd mango kiwi peach"

在第一个芒果上劈开:

"abcd mango kiwi peach"

如果要在最后一次出现时进行分割,请参阅python中的分区字符串,并获取冒号后的最后一段的值。


当前回答

从文档中可以看出:

str.split ([[, maxsplit]] 9月) 返回字符串中的单词列表,使用sep作为分隔符字符串。如果给出了maxsplit,则最多进行maxsplit次拆分(因此,列表将最多有maxsplit+1个元素)。

s.split('mango', 1)[1]

其他回答

对我来说,更好的方法是:

s.split('mango', 1)[-1]

...因为如果发生了发生不在字符串中,你会得到“IndexError: list index out of range”。

因此-1将不会受到任何伤害,因为发生的次数已经设置为1。

从文档中可以看出:

str.split ([[, maxsplit]] 9月) 返回字符串中的单词列表,使用sep作为分隔符字符串。如果给出了maxsplit,则最多进行maxsplit次拆分(因此,列表将最多有maxsplit+1个元素)。

s.split('mango', 1)[1]

总结

最简单和性能最好的方法是使用字符串的.partition方法。

通常,人们可能希望获得所找到的分隔符之前或之后的部分,并且可能希望找到字符串中分隔符的第一个或最后一个出现的部分。对于大多数技术,所有这些可能性都大致一样简单,从一种转换到另一种也很简单。

对于下面的例子,我们假设:

>>> import re
>>> s = '123mango abcd mango kiwi peach'

使用.split

>>> s.split('mango', 1)
['123', ' abcd mango kiwi peach']

split的第二个参数限制拆分字符串的次数。这给出了分隔符之前和之后的部分;然后我们可以选择我们想要的。

如果分隔符没有出现,则不进行拆分:

>>> s.split('grape', 1)
['123mango abcd mango kiwi peach']
Thus, to check whether the delimiter was present, check the length of the result before working with it.

使用.partition

>>> s.partition('mango')
('123', 'mango', ' abcd mango kiwi peach')

结果是一个元组,并且在找到分隔符时保留分隔符本身。

当没有找到分隔符时,结果将是一个相同长度的元组,结果中有两个空字符串:

>>> s.partition('grape')
('123mango abcd mango kiwi peach', '', '')

因此,要检查分隔符是否存在,请检查第二个元素的值。

使用正则表达式

>>> # Using the top-level module functionality
>>> re.split(re.escape('mango'), s, 1)
['123', ' abcd mango kiwi peach']
>>> # Using an explicitly compiled pattern
>>> mango = re.compile(re.escape('mango'))
>>> mango.split(s, 1)
['123', ' abcd mango kiwi peach']

正则表达式的.split方法具有与内置字符串的.split方法相同的参数,以限制分割的数量。同样,当分隔符不出现时,不会进行拆分:

>>> grape = re.compile(re.escape('grape'))
>>> grape.split(s, 1)
['123mango abcd mango kiwi peach']

在这些示例中,re.escape没有任何作用,但在一般情况下,为了将分隔符指定为文字文本,必须使用它。另一方面,使用re模块打开了正则表达式的全部功能:

>>> vowels = re.compile('[aeiou]')
>>> # Split on any vowel, without a limit on the number of splits:
>>> vowels.split(s)
['123m', 'ng', ' ', 'bcd m', 'ng', ' k', 'w', ' p', '', 'ch']

(注意空字符串:在桃的e和a之间。)

使用索引和切片

使用字符串的.index方法找出分隔符的位置,然后用它进行切片:

>>> s[:s.index('mango')] # for everything before the delimiter
'123'
>>> s[s.index('mango')+len('mango'):] # for everything after the delimiter
' abcd mango kiwi peach'

这直接给出了前缀。但是,如果没有找到分隔符,则会引发一个异常:

>>> s[:s.index('grape')]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

而是在最后一个事件之后

虽然没有被问到,但我在这里介绍了相关的技术,以供参考。

.split和.partition技术有直接对应的部分,用于获取字符串的最后一部分(即分隔符最后出现之后的所有内容)。供参考:

>>> '123mango abcd mango kiwi peach'.rsplit('mango', 1)
['123mango abcd ', ' kiwi peach']
>>> '123mango abcd mango kiwi peach'.rpartition('mango')
('123mango abcd ', 'mango', ' kiwi peach')

类似地,有一个.rindex来匹配.index,但它仍然会给出分区最后一次匹配开始的索引。因此:

>>> s[:s.rindex('mango')] # everything before the last match
'123mango abcd '
>>> s[s.rindex('mango')+len('mango'):] # everything after the last match
' kiwi peach'

对于正则表达式方法,我们可以求助于反转输入的技术,寻找反转分隔符的第一次出现,反转各个结果,反转结果列表:

>>> ognam = re.compile(re.escape('mango'[::-1]))
>>> [x[::-1] for x in ognam.split('123mango abcd mango kiwi peach'[::-1], 1)][::-1]
['123mango abcd ', ' kiwi peach']

当然,这几乎肯定是比它的价值更多的努力。

另一种方法是使用从分隔符到字符串结尾的负向前查找:

>>> literal_mango = re.escape('mango')
>>> last_mango = re.compile(f'{literal_mango}(?!.*{literal_mango})')
>>> last_mango.split('123mango abcd mango kiwi peach', 1)
['123mango abcd ', ' kiwi peach']

由于前瞻,这是一个O(n²)的最坏情况算法。

性能测试

$ python -m timeit --setup="s='123mango abcd mango kiwi peach'" "s.partition('mango')[-1]"
2000000 loops, best of 5: 128 nsec per loop
$ python -m timeit --setup="s='123mango abcd mango kiwi peach'" "s.split('mango', 1)[-1]"
2000000 loops, best of 5: 157 nsec per loop
$ python -m timeit --setup="s='123mango abcd mango kiwi peach'" "s[s.index('mango')+len('mango'):]"
1000000 loops, best of 5: 250 nsec per loop
$ python -m timeit --setup="s='123mango abcd mango kiwi peach'; import re; mango=re.compile(re.escape('mango'))" "mango.split(s, 1)[-1]"
1000000 loops, best of 5: 258 nsec per loop

虽然更灵活,但正则表达式方法肯定更慢。限制分割的数量可以提高字符串方法和正则表达式的性能(没有限制的计时没有显示,因为它们更慢,也会给出不同的结果),但.partition仍然是一个明显的赢家。

对于这个测试数据,.index方法较慢,尽管它只需要创建一个子字符串,并且不需要遍历匹配之外的文本(为了创建其他子字符串)。预先计算分隔符的长度是有帮助的,但这仍然比.split和.partition方法慢。

>>> s = "123mango abcd mango kiwi peach"
>>> s.split("mango", 1)
['123', ' abcd mango kiwi peach']
>>> s.split("mango", 1)[1]
' abcd mango kiwi peach'

你也可以使用str.partition:

>>> text = "123mango abcd mango kiwi peach"

>>> text.partition("mango")
('123', 'mango', ' abcd mango kiwi peach')

>>> text.partition("mango")[-1]
' abcd mango kiwi peach'

>>> text.partition("mango")[-1].lstrip()  # if whitespace strip-ing is needed
'abcd mango kiwi peach'

使用str.partition的好处是它总是会返回一个如下形式的元组:

(<pre>, <separator>, <post>)

因此,这使得解包输出非常灵活,因为结果元组中总是有3个元素。