如何拆分,但在Python中引用的string中忽略分隔符?

我需要用分号分隔这样一个string。 但我不想分割在string('或')内的分号。我不parsing文件;只是一个没有换行符的简单string。

part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5

结果应该是:

  • 第1部分
  • “这是;第二部分;”
  • '这是 ; 第3部分“
  • 第4部分
  • 这是“部分”5

我想这可以用正则expression式来完成,但是如果没有的话, 我接受另一种方法。

大部分的答案似乎大大复杂。 你不需要返回引用。 你不需要依赖是否re.findall给重叠匹配。 鉴于input不能用csv模块parsing,所以正则expression式是唯一的方法,所有你需要的是调用与匹配字段的模式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>;' 其中<marker>必须是一些string(不含分号),您知道在分割之前不会在数据中出现。 还需要在以下情况下恢复数据:

 >>> marker = ";!$%^&;" >>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]] ['aaa', '', 'aaa', "'b;;b'"] 

然而,这是一个杂食。 有更好的build议吗?

 re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data) 

每次find分号时,向前扫描整个剩余的string,确保有偶数个单引号和偶数个双引号。 (双引号字段中的单引号,或反之亦然,将被忽略。)如果预读成功,则分号是分隔符。

不像邓肯的解决scheme ,它匹配的领域而不是分隔符,这一个没有空字段的问题。 (甚至不是最后一个:与许多其他的split实现不同,Python不会自动丢弃尾随的空字段。)

这是一个注释的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”parsing为:

 ['a', 'b', 'c'] 

您似乎有一个分号分隔的string。 为什么不使用csv模块来做所有的辛苦工作?

closures我的头顶,这应该工作

 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")

编辑:
不幸的是,由于混合的string引号(单个和双个),这并不是很有效,(即使您使用StringIO,也是如此)。 你真正得到的是

['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']

如果你可以改变数据在适当的地方只包含单引号或双引号,它应该可以正常工作,但是这样做会否定这个问题。

 >>> 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'] 

虽然可以通过lookaheads / behinds / backreferences使用PCRE完成,但由于需要匹配平衡的引号对,所以实际上并不是真正的正则expression式的devise任务。

相反,最好只是制作一个迷你状态机,并通过stringparsing。

编辑

事实certificate,由于保证了非重叠匹配的Python re.findall方便的附加function,在Python中使用正则expression式可能会比使用Python更直接。 详情请参阅评论。

但是,如果您对非正则expression式的实现可能是什么样子感兴趣:

 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'] 
 >>> 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'] 

这个正则expression式会这样做: (?:^|;)("(?:[^"]+|"")*"|[^;]*)

既然你没有'\ n',用它来代替';' 不在引号string中

 >>> new_s = '' >>> is_open = False >>> for c in s: ... if c == ';' and not is_open: ... c = '\n' ... elif c in ('"',"'"): ... is_open = not is_open ... new_s += c >>> result = new_s.split('\n') >>> result ['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5'] 

即使我确定有一个干净的正则expression式的解决scheme(到目前为止,我喜欢@ noiflection的答案),这是一个快速和肮脏的非正则expression式的答案。

 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'] 

(我从来没有把这种东西放在一起,随时批评我的表格!)

我的方法是用不会出现在文本中的另一个字符replace分号的所有非引号出现,然后分割该字符。 下面的代码使用带函数参数的re.sub函数来search和replace所有出现的srchstring,而不是用单引号或双引号或括号,括号或大括号括起来,用一个replstring:

 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 

如果你不关心括号内的字符,你可以简化这些代码。
假设你想用pipe道或竖条作为替代字符,你可以这样做:

 mylist = srchrepl(';', '|', mytext).split('|') 

顺便说一句,这使用Python 3.1的nonlocal ,如果你需要改变它为全球。

一个通用的解决scheme:

 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", ''] 

此解决scheme:

  • 捕获所有空组(包括开始和结束时)
  • 适用于大多数stream行的分隔符,包括空格,制表符和逗号
  • 将其他types的引号内的引号视为非特殊字符
  • 如果遇到不匹配的未加引号的引用,则将引用的行的剩余部分视为引用

我们可以创build一个自己的function

 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 

这在我看来是一个半雅的解决scheme。

新解决scheme:

 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 

旧解决scheme:

我select匹配,如果有一个开盘报价,并等待它closures,并匹配结束分号。 要匹配的每个“部分”都需要以分号结尾。 所以这匹配的东西是这样的:

  • '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,但它包含你想要的。