在 Python 中,如何拆分字符串并保留分隔符?

Posted

技术标签:

【中文标题】在 Python 中,如何拆分字符串并保留分隔符?【英文标题】:In Python, how do I split a string and keep the separators? 【发布时间】:2011-01-09 08:14:03 【问题描述】:

这是解释这一点的最简单方法。这是我正在使用的:

re.split('\W', 'foo/bar spam\neggs')
-> ['foo', 'bar', 'spam', 'eggs']

这就是我想要的:

someMethod('\W', 'foo/bar spam\neggs')
-> ['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

原因是我想将一个字符串拆分为标记,对其进行操作,然后将其重新组合在一起。

【问题讨论】:

\W 代表什么?我在谷歌上失败了。 一个非单词字符see here for details 对于应用于原始字节字符串并归结为“拆分字符串并将分隔符保留为拆分字符串块的一部分,而不是单独的列表元素”的问题,请参阅***.com/questions/62591863/…跨度> 【参考方案1】:
>>> re.split('(\W)', 'foo/bar spam\neggs')
['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

【讨论】:

这很酷。我不知道 re.split 对捕获组做到了这一点。 @Laurence:嗯,它有文档记录:docs.python.org/library/re.html#re.split:“按出现的模式分割字符串。如果在模式中使用捕获括号,那么模式中所有组的文本也返回为结果列表的一部分。” 它的文档严重不足。我已经使用 Python 14 年了,才发现这一点。 是否有一个选项可以将组匹配的输出附加到拆分左侧(或类似右侧)的任何内容?例如,这是否可以轻松修改为输出为['foo', '/bar', ' spam', '\neggs'] @Mr.F 你也许可以用 re.sub 做点什么。我想按结束百分比拆分,所以我只是替换为双字符,然后拆分,hacky,但适用于我的情况:re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot')) --> ['5.000%', 'Additional Whatnot']【参考方案2】:

你也可以用字符串数组而不是正则表达式来分割字符串,像这样:

def tokenizeString(aString, separators):
    #separators is an array of strings that are being used to split the string.
    #sort separators in order of descending length
    separators.sort(key=len)
    listToReturn = []
    i = 0
    while i < len(aString):
        theSeparator = ""
        for current in separators:
            if current == aString[i:i+len(current)]:
                theSeparator = current
        if theSeparator != "":
            listToReturn += [theSeparator]
            i = i + len(theSeparator)
        else:
            if listToReturn == []:
                listToReturn = [""]
            if(listToReturn[-1] in separators):
                listToReturn += [""]
            listToReturn[-1] += aString[i]
            i += 1
    return listToReturn
    

print(tokenizeString(aString = "\"\"\"hi\"\"\" hello + world += (1*2+3/5) '''hi'''", separators = ["'''", '+=', '+', "/", "*", "\\'", '\\"', "-=", "-", " ", '"""', "(", ")"]))

【讨论】:

【参考方案3】:
# This keeps all separators  in result 
##########################################################################
import re
st="%%(c+dd+e+f-1523)%%7"
sh=re.compile('[\+\-//\*\<\>\%\(\)]')

def splitStringFull(sh, st):
   ls=sh.split(st)
   lo=[]
   start=0
   for l in ls:
     if not l : continue
     k=st.find(l)
     llen=len(l)
     if k> start:
       tmp= st[start:k]
       lo.append(tmp)
       lo.append(l)
       start = k + llen
     else:
       lo.append(l)
       start =llen
   return lo
  #############################

li= splitStringFull(sh , st)
['%%(', 'c', '+', 'dd', '+', 'e', '+', 'f', '-', '1523', ')%%', '7']

【讨论】:

【参考方案4】:

另一个适用于 Python 3 的无正则表达式解决方案

# Split strings and keep separator
test_strings = ['<Hello>', 'Hi', '<Hi> <Planet>', '<', '']

def split_and_keep(s, sep):
   if not s: return [''] # consistent with string.split()

   # Find replacement character that is not used in string
   # i.e. just use the highest available character plus one
   # Note: This fails if ord(max(s)) = 0x10FFFF (ValueError)
   p=chr(ord(max(s))+1) 

   return s.replace(sep, sep+p).split(p)

for s in test_strings:
   print(split_and_keep(s, '<'))


# If the unicode limit is reached it will fail explicitly
unicode_max_char = chr(1114111)
ridiculous_string = '<Hello>'+unicode_max_char+'<World>'
print(split_and_keep(ridiculous_string, '<'))

【讨论】:

【参考方案5】:

如果您在换行符上拆分,请使用splitlines(True)

>>> 'line 1\nline 2\nline without newline'.splitlines(True)
['line 1\n', 'line 2\n', 'line without newline']

(不是一般的解决方案,但在此处添加,以防有人来到这里没有意识到此方法的存在。)

【讨论】:

【参考方案6】:

如果想拆分字符串,同时通过正则表达式保留分隔符而不捕获组:

def finditer_with_separators(regex, s):
    matches = []
    prev_end = 0
    for match in regex.finditer(s):
        match_start = match.start()
        if (prev_end != 0 or match_start > 0) and match_start != prev_end:
            matches.append(s[prev_end:match.start()])
        matches.append(match.group())
        prev_end = match.end()
    if prev_end < len(s):
        matches.append(s[prev_end:])
    return matches

regex = re.compile(r"[\(\)]")
matches = finditer_with_separators(regex, s)

如果假设正则表达式被包装到捕获组中:

def split_with_separators(regex, s):
    matches = list(filter(None, regex.split(s)))
    return matches

regex = re.compile(r"([\(\)])")
matches = split_with_separators(regex, s)

这两种方法都可以删除在大多数情况下无用且烦人的空组。

【讨论】:

这对我来说很完美。感谢您的贡献!【参考方案7】:

如果你只有 1 个分隔符,你可以使用列表推导:

text = 'foo,bar,baz,qux'  
sep = ','

附加/前置分隔符:

result = [x+sep for x in text.split(sep)]
#['foo,', 'bar,', 'baz,', 'qux,']
# to get rid of trailing
result[-1] = result[-1].strip(sep)
#['foo,', 'bar,', 'baz,', 'qux']

result = [sep+x for x in text.split(sep)]
#[',foo', ',bar', ',baz', ',qux']
# to get rid of trailing
result[0] = result[0].strip(sep)
#['foo', ',bar', ',baz', ',qux']

分隔符作为它自己的元素:

result = [u for x in text.split(sep) for u in (x, sep)]
#['foo', ',', 'bar', ',', 'baz', ',', 'qux', ',']
results = result[:-1]   # to get rid of trailing

【讨论】:

也可以添加if x,保证split产生的chunk有一些内容,即result = [x + sep for x in text.split(sep) if x] 对我来说,剥离太多,我不得不使用这个:result = [sep+x for x in data.split(sep)]result[0] = result[0][len(sep):]【参考方案8】:

另一个例子,在非字母数字上拆分并保留分隔符

import re
a = "foo,bar@candy*ice%cream"
re.split('([^a-zA-Z0-9])',a)

输出:

['foo', ',', 'bar', '@', 'candy', '*', 'ice', '%', 'cream']

解释

re.split('([^a-zA-Z0-9])',a)

() <- keep the separators
[] <- match everything in between
^a-zA-Z0-9 <-except alphabets, upper/lower and numbers.

【讨论】:

尽管正如docs 所说,这等同于公认的答案,但我喜欢这个版本的可读性——尽管\W 是一种更紧凑的表达方式。 我也喜欢它的可读性,如果你想包含/排除一些字符,你可以自定义它! 对于使用标点符号作为单词一部分的“疯狂”语言也更好。一些希伯来语单词有 ' 或 " 内置(כפר אז"ר, ג'ירף),需要特殊处理。【参考方案9】:

一个懒惰且简单的解决方案

假设您的正则表达式模式是split_pattern = r'(!|\?)'

首先,添加一些与新分隔符相同的字符,例如 '[cut]'

new_string = re.sub(split_pattern, '\\1[cut]', your_string)

然后你拆分新的分隔符new_string.split('[cut]')

【讨论】:

这种方法很聪明,但是当原始字符串在某处已经包含[cut]时会失败。 它在大规模问题上可能更快,因为它最终使用 string.split(),以防 re.split() 的成本高于 re.sub() 和 string.split()(其中我不知道)。【参考方案10】:

我在尝试拆分文件路径时遇到了类似的问题,并且很难找到一个简单的答案。 这对我有用,并且不需要将分隔符替换回拆分文本:

my_path = 'folder1/folder2/folder3/file1'

import re

re.findall('[^/]+/|[^/]+', my_path)

返回:

['folder1/', 'folder2/', 'folder3/', 'file1']

【讨论】:

这可以通过使用:re.findall('[^/]+/?', my_path) 稍微简化(例如,使用? 使尾部斜杠成为可选,而不是使用| 提供两个替代方案。 对于路径,最好使用 stdlib os.path 函数【参考方案11】:

我发现这种基于生成器的方法更令人满意:

def split_keep(string, sep):
    """Usage:
    >>> list(split_keep("a.b.c.d", "."))
    ['a.', 'b.', 'c.', 'd']
    """
    start = 0
    while True:
        end = string.find(sep, start) + 1
        if end == 0:
            break
        yield string[start:end]
        start = end
    yield string[start:]

它避免了找出正确的正则表达式的需要,而理论上应该相当便宜。它不会创建新的字符串对象,而是将大部分迭代工作委托给高效的 find 方法。

...在 Python 3.8 中它可以短到:

def split_keep(string, sep):
    start = 0
    while (end := string.find(sep, start) + 1) > 0:
        yield string[start:end]
        start = end
    yield string[start:]

【讨论】:

【参考方案12】:

    将所有seperator: (\W)替换为seperator + new_seperator: (\W;)

    new_seperator: (;)分割

def split_and_keep(seperator, s):
  return re.split(';', re.sub(seperator, lambda match: match.group() + ';', s))

print('\W', 'foo/bar spam\neggs')

【讨论】:

是的,这样更好,虽然切换添加 ';' 的顺序是可行的。【参考方案13】:

这是一个简单的.split 解决方案,无需正则表达式。

这是Python split() without removing the delimiter 的答案,因此与原始帖子的要求不完全一样,但另一个问题已作为此问题的副本关闭。

def splitkeep(s, delimiter):
    split = s.split(delimiter)
    return [substr + delimiter for substr in split[:-1]] + [split[-1]]

随机测试:

import random

CHARS = [".", "a", "b", "c"]
assert splitkeep("", "X") == [""]  # 0 length test
for delimiter in ('.', '..'):
    for _ in range(100000):
        length = random.randint(1, 50)
        s = "".join(random.choice(CHARS) for _ in range(length))
        assert "".join(splitkeep(s, delimiter)) == s

【讨论】:

出于速度原因,在大规模问题上应避免使用正则表达式,这就是为什么这是一个很好的提示。【参考方案14】:

我可以把它留在这里

s = 'foo/bar spam\neggs'
print(s.replace('/', '+++/+++').replace(' ', '+++ +++').replace('\n', '+++\n+++').split('+++'))

['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

【讨论】:

【参考方案15】:

使用 re.split 并且你的正则表达式来自变量并且你有多个分隔符,你可以使用如下:

# BashSpecialParamList is the special param in bash,
# such as your separator is the bash special param
BashSpecialParamList = ["$*", "$@", "$#", "$?", "$-", "$$", "$!", "$0"]
# aStr is the the string to be splited
aStr = "$a Klkjfd$0 $? $#%$*Sdfdf"

reStr = "|".join([re.escape(sepStr) for sepStr in BashSpecialParamList])

re.split(f'(reStr)', aStr)

# Then You can get the result:
# ['$a Klkjfd', '$0', ' ', '$?', ' ', '$#', '%', '$*', 'Sdfdf']

参考:GNU Bash Special Parameters

【讨论】:

【参考方案16】:

通过执行安装 wrs“不移除分隔符”

pip install wrs

(由 Rao Hamza 开发)

import wrs
text  = "Now inbox “how to make spam ad” Invest in hard email marketing."
splitor = 'email | spam | inbox'
list = wrs.wr_split(splitor, text)
print(list)

结果: ['现在','收件箱'如何制作','垃圾邮件广告','电子邮件营销']

【讨论】:

【参考方案17】:

之前发布的其中一些答案会重复分隔符,或者在我的案例中遇到其他一些错误。您可以改为使用此功能:

def split_and_keep_delimiter(input, delimiter):
    result      = list()
    idx         = 0
    while delimiter in input:
        idx     = input.index(delimiter);
        result.append(input[0:idx+len(delimiter)])
        input = input[idx+len(delimiter):]
    result.append(input)
    return result

【讨论】:

以上是关于在 Python 中,如何拆分字符串并保留分隔符?的主要内容,如果未能解决你的问题,请参考以下文章

如何拆分字符串但在开头保留分隔符[重复]

如何拆分字符串但在java中保留分隔符? [复制]

字符串入门十八讲合集四

字符串入门十八讲合集四

Javascript 和正则表达式:拆分字符串并保留分隔符

Python RE库字符串拆分,但将分隔符/分隔符保留为下一个字符串的一部分