在 python 中,如何拆分但忽略引号字符串中的分隔符?
- 2025-02-27 09:06:00
- admin 原创
- 59
问题描述:
我需要像这样用分号分割字符串。但我不想用字符串内部的分号(' 或 ")进行分割。我不是在解析文件;只是一个没有换行符的简单字符串。
part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5
结果应该是:
第一部分
“这是;第二部分;”
“这是;第 3 部分”
第 4 部分
这是“部分” 5
我认为这可以用正则表达式来完成,但如果不可以,我愿意尝试另一种方法。
解决方案 1:
大多数答案似乎过于复杂。您不需要反向引用。您不需要依赖 re.findall 是否提供重叠匹配。鉴于无法使用 csv 模块解析输入,因此正则表达式几乎是唯一的方法,您只需要使用与字段匹配的模式调用 re.split。
请注意,这里匹配字段比匹配分隔符要容易得多:
import re
data = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
PATTERN = re.compile(r'''((?:[^;"']|"[^"]*"|'[^']*')+)''')
print PATTERN.split(data)[1::2]
输出为:
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
正如 Jean-Luc Nacif Coelho 正确指出的那样,这无法正确处理空组。根据情况,这可能很重要,也可能不重要。如果确实很重要,则可以通过以下方式处理它,例如,';;'
用';<marker>;'
where替换<marker>
某些字符串(不带分号),您知道这些字符串在拆分之前不会出现在数据中。您还需要在以下情况下恢复数据:
>>> marker = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]
然而这只是一个临时解决方案。还有其他更好的建议吗?
解决方案 2:
re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)
每次找到分号时,前瞻函数都会扫描剩余的整个字符串,确保单引号和双引号的数量均为偶数。(双引号字段内的单引号或反之亦然,将被忽略。)如果前瞻函数成功,则分号为分隔符。
与Duncan 的解决方案(该解决方案匹配字段而不是分隔符)不同,此解决方案可以解决空字段问题。(甚至最后一个解决方案也没有问题:与许多其他split
实现不同,Python 不会自动丢弃尾随的空字段。)
解决方案 3:
>>> a='A,"B,C",D'
>>> a.split(',')
['A', '"B', 'C"', 'D']
It failed. Now try csv module
>>> import csv
>>> from StringIO import StringIO
>>> data = StringIO(a)
>>> data
<StringIO.StringIO instance at 0x107eaa368>
>>> reader = csv.reader(data, delimiter=',')
>>> for row in reader: print row
...
['A,"B,C",D']
解决方案 4:
以下是带注释的pyparsing方法:
from pyparsing import (printables, originalTextFor, OneOrMore,
quotedString, Word, delimitedList)
# unquoted words can contain anything but a semicolon
printables_less_semicolon = printables.replace(';','')
# capture content between ';'s, and preserve original text
content = originalTextFor(
OneOrMore(quotedString | Word(printables_less_semicolon)))
# process the string
print delimitedList(content, ';').parseString(test)
给予
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4',
'this "is ; part" 5']
通过使用 pyparsing 提供的功能quotedString
,您还可以获得对转义引号的支持。
您还不清楚如何处理分号分隔符之前或之后的前导空格,并且示例文本中的所有字段都没有。Pyparsing 会将“a; b ; c”解析为:
['a', 'b', 'c']
解决方案 5:
您似乎有一个分号分隔的字符串。为什么不使用csv
模块来完成所有艰苦的工作?
我突然想到,这应该可行
import csv
from StringIO import StringIO
line = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
data = StringIO(line)
reader = csv.reader(data, delimiter=';')
for row in reader:
print row
这应该会给你类似
("part 1", "this is ; part 2;", 'this is ; part 3', "part 4", "this "is ; part" 5")
编辑:
不幸的是,由于混合了字符串引号(单引号和双引号),因此这种方法不太管用(即使您按照我的意图使用了 StringIO)。您实际得到的是
['part 1', 'this is ; part 2;', "'this is ", " part 3'", 'part 4', 'this "is ', ' part" 5']
。
如果您可以将数据更改为仅在适当的位置包含单引号或双引号,它应该可以正常工作,但是这有点否定了这个问题。
解决方案 6:
>>> x = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> import re
>>> re.findall(r'''(?:[^;'"]+|'(?:[^']|\\.)*'|"(?:[^']|\\.)*")+''', x)
['part 1', "this is ';' part 2", "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
解决方案 7:
虽然它可以通过前瞻/后瞻/反向引用用 PCRE 来完成,但由于需要匹配平衡的引号对,这实际上并不是正则表达式设计的任务。
相反,最好的办法可能是只制作一个微型状态机并像那样解析字符串。
编辑
事实证明,由于 Python 的附加功能re.findall
可以保证不重叠匹配,因此使用 Python 中的正则表达式可以比其他方式更直接地完成此操作。有关详细信息,请参阅注释。
但是,如果你对非正则表达式的实现感到好奇:
x = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
results = [[]]
quote = None
for c in x:
if c == "'" or c == '"':
if c == quote:
quote = None
elif quote == None:
quote = c
elif c == ';':
if quote == None:
results.append([])
continue
results[-1].append(c)
results = [''.join(x) for x in results]
# results = ['part 1', '"this is ; part 2;"', "'this is ; part 3'",
# 'part 4', 'this "is ; part" 5']
解决方案 8:
由于您没有 '\n',因此请使用它来替换引号字符串中不存在的任何 ';'
>>> new_s = ''
>>> is_open = False
>>> for c in s:
... if c == ';' and not is_open:
... c = '
'
... elif c in ('"',"'"):
... is_open = not is_open
... new_s += c
>>> result = new_s.split('
')
>>> result
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
解决方案 9:
我们可以创建一个自己的函数
def split_with_commas_outside_of_quotes(string):
arr = []
start, flag = 0, False
for pos, x in enumerate(string):
if x == '"':
flag= not(flag)
if flag == False and x == ',':
arr.append(string[start:pos])
start = pos+1
arr.append(string[start:pos])
return arr
解决方案 10:
这个正则表达式将执行以下操作:(?:^|;)("(?:[^"]+|"")*"|[^;]*)
解决方案 11:
通用解决方案:
import re
regex = '''(?:(?:[^{0}"']|"[^"]*(?:"|$)|'[^']*(?:'|$))+|(?={0}{0})|(?={0}$)|(?=^{0}))'''
delimiter = ';'
data2 = ''';field 1;"field 2";;'field;4';;;field';'7;'''
field = re.compile(regex.format(delimiter))
print(field.findall(data2))
输出:
['', 'field 1', '"field 2"', '', "'field;4'", '', '', "field';'7", '']
此解决方案:
捕获所有空组(包括开头和结尾)
适用于大多数常用分隔符,包括空格、制表符和逗号
将其他类型的引号内的引号视为非特殊字符
如果遇到不匹配的未加引号的引号,则将该行的其余部分视为带引号的引号
解决方案 12:
尽管这个主题已经很老了,而且以前的答案也很好用,但我还是提出了自己在 python 中实现 split 函数的方法。
如果您不需要处理大量字符串并且易于定制,那么这种方法很有效。
这是我的功能:
# l is string to parse;
# splitchar is the separator
# ignore char is the char between which you don't want to split
def splitstring(l, splitchar, ignorechar):
result = []
string = ""
ignore = False
for c in l:
if c == ignorechar:
ignore = True if ignore == False else False
elif c == splitchar and not ignore:
result.append(string)
string = ""
else:
string += c
return result
因此你可以运行:
line= """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
splitted_data = splitstring(line, ';', '"')
结果:
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
优点是该函数适用于空字段和字符串中任意数量的分隔符。
希望这有帮助!
解决方案 13:
尽管我确信有一个干净的正则表达式解决方案(到目前为止我喜欢@noiflection 的答案),但这是一个快速而肮脏的非正则表达式答案。
s = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
inQuotes = False
current = ""
results = []
currentQuote = ""
for c in s:
if not inQuotes and c == ";":
results.append(current)
current = ""
elif not inQuotes and (c == '"' or c == "'"):
currentQuote = c
inQuotes = True
elif inQuotes and c == currentQuote:
currentQuote = ""
inQuotes = False
else:
current += c
results.append(current)
print results
# ['part 1', 'this is ; part 2;', 'this is ; part 3', 'part 4', 'this is ; part 5']
(我从来没有做过这种事情,请随意批评我的形式!)
解决方案 14:
我的方法是将所有未加引号的分号替换为文本中永远不会出现的另一个字符,然后根据该字符进行拆分。以下代码使用带有函数参数的 re.sub 函数来搜索和替换所有srch
未用单引号或双引号或括号、方括号或大括号括起来的字符串repl
:
def srchrepl(srch, repl, string):
"""
Replace non-bracketed/quoted occurrences of srch with repl in string.
"""
resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
+ srch + """])|(?P<rbrkt>[)]}])""")
return resrchrepl.sub(_subfact(repl), string)
def _subfact(repl):
"""
Replacement function factory for regex sub method in srchrepl.
"""
level = 0
qtflags = 0
def subf(mo):
nonlocal level, qtflags
sepfound = mo.group('sep')
if sepfound:
if level == 0 and qtflags == 0:
return repl
else:
return mo.group(0)
elif mo.group('lbrkt'):
if qtflags == 0:
level += 1
return mo.group(0)
elif mo.group('quote') == "'":
qtflags ^= 1 # toggle bit 1
return "'"
elif mo.group('quote') == '"':
qtflags ^= 2 # toggle bit 2
return '"'
elif mo.group('rbrkt'):
if qtflags == 0:
level -= 1
return mo.group(0)
return subf
如果您不关心括号字符,则可以大大简化此代码。
假设您想使用管道或竖线作为替代字符,您可以这样做:
mylist = srchrepl(';', '|', mytext).split('|')
顺便说一句,这是nonlocal
从 Python 3.1 开始使用的,如果需要的话,可以将其更改为全局。
解决方案 15:
无需按分隔符模式进行拆分,只需捕获您需要的内容:
>>> import re
>>> data = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> re.findall(r';([\'"][^\'"]+[\'"]|[^;]+)', ';' + data)
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ', ' part" 5']
解决方案 16:
最简单的方法是使用 shlex (简单词法分析)——Python 中的内置模块
import shlex
shlex.split("""part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5 """ )
['part',
'1;this is ; part 2;;this is ; part 3;part',
'4;this',
'is ; part',
'5']
解决方案 17:
在我看来这是一个还算优雅的解决方案。
新解决方案:
import re
reg = re.compile('(\'|").*?\\1')
pp = re.compile('.*?;')
def splitter(string):
#add a last semicolon
string += ';'
replaces = []
s = string
i = 1
#replace the content of each quote for a code
for quote in reg.finditer(string):
out = string[quote.start():quote.end()]
s = s.replace(out, '**' + str(i) + '**')
replaces.append(out)
i+=1
#split the string without quotes
res = pp.findall(s)
#add the quotes again
#TODO this part could be faster.
#(lineal instead of quadratic)
i = 1
for replace in replaces:
for x in range(len(res)):
res[x] = res[x].replace('**' + str(i) + '**', replace)
i+=1
return res
旧解决方案:
我选择匹配是否有一个开头的引号并等待它结束,并且匹配一个结束的分号。你想要匹配的每个“部分”都需要以分号结尾。所以这个匹配是这样的:
'foobar;.sska';
“akjshd;asjkdhkj……”;
asdkjhakjhajsd.jhdf;
代码:
mm = re.compile('''((?P<quote>'|")?.*?(?(quote)\\2|);)''')
res = mm.findall('''part 1;"this is ; part 2;";'this is ; part 3';part 4''')
您可能需要对 res 进行一些后期处理,但它包含您想要的内容。
扫码咨询,免费领取项目管理大礼包!