在python中,如何分割但忽略带引号字符串中的分隔符?

在python中,如何分割但忽略带引号字符串中的分隔符?,python,regex,Python,Regex,我需要像这样拆分一个字符串,用分号。但我不想在字符串('or')中使用分号进行拆分。我不是在解析文件,只是一个没有换行符的简单字符串 第1部分;“这是;第二部分;这是;第三部分;第四部分;这是;第5部分 结果应该是: 第一部分 "这是,;第二部分;" “这是第三部分” 第四部分 这"是,;第5部分 我想这可以用正则表达式来完成,但如果不行的话,我愿意用另一种方法。这个正则表达式可以做到:(?:^ |)((?:[^]+|“”)*“|[^;]*),而它可以通过lookaheads/behinds

我需要像这样拆分一个字符串,用分号。但我不想在字符串('or')中使用分号进行拆分。我不是在解析文件,只是一个没有换行符的简单字符串

第1部分;“这是;第二部分;这是;第三部分;第四部分;这是;第5部分

结果应该是:

  • 第一部分
  • "这是,;第二部分;"
  • “这是第三部分”
  • 第四部分
  • 这"是,;第5部分

我想这可以用正则表达式来完成,但如果不行的话,我愿意用另一种方法。

这个正则表达式可以做到:
(?:^ |)((?:[^]+|“”)*“|[^;]*)
,而它可以通过lookaheads/behinds/backreferences用PCRE来完成,由于需要匹配平衡的引号对,所以它实际上不是regex设计的任务

相反,最好只制作一个迷你状态机并像那样解析字符串

编辑 事实证明,由于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']

在我看来,这是一个半优雅的解决方案

新解决方案: 旧的解决方案: 我选择匹配是否有开始引用,并等待它结束,然后匹配一个结束分号。要匹配的每个“部分”都需要以分号结尾。 因此,这与以下内容相匹配:

  • “foobar;”;。sska'
  • “akjshd;asjkdhkj.”
  • asdkjhajsd.jhdf
代码:

mm=re.compile('''(?P'|')?*?(?(引号)\\2 |)''')
res=mm.findall(“‘第一部分’;这是;第二部分;“;‘这是;第三部分’;第四部分’)

您可能需要对res进行一些后处理,但它包含了您想要的内容。

尽管我确信有一个干净的正则表达式解决方案(到目前为止,我喜欢@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']

(我从来没有整理过这样的东西,请随意评论我的表格!)

你似乎有一个分号分隔的字符串。为什么不使用
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 
这会给你一些类似于
(“第一部分”、“这是;第二部分”、“这是;第三部分”、“第四部分”、“这是;第五部分”)

编辑:
不幸的是,由于混合的字符串引号(单引号和双引号),这并不能很好地工作(即使您确实按照我的意图使用了StringIO)

[“第一部分”、“这是;第二部分”、“这是”、“第三部分”、“第四部分”、“这是”、“第五部分”]


如果您可以将数据更改为仅在适当的位置包含单引号或双引号,那么它应该可以正常工作,但这一点有点否定了这个问题。

我的方法是用另一个永远不会出现在文本中的字符替换所有未引用的分号,然后在该字符上拆分。下面的代码将re.sub函数与函数参数一起使用,以
repl
字符串搜索并替换所有出现的
srch
字符串(未包含在单引号或双引号或括号、括号或大括号中):

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

顺便说一句,它使用Python 3.1中的
非本地
,如果需要,可以将其更改为全局。

大多数答案似乎过于复杂。您不需要反向引用。您不需要依赖于re.findall是否提供重叠匹配。鉴于无法使用c解析输入sv模块,所以正则表达式是非常好的唯一方法,您只需使用匹配字段的模式调用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 = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]
然而,这是一个难题。有更好的建议吗

re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)
每次查找分号时,先行搜索都会扫描剩余的整个字符串,确保有偶数个单引号和偶数个双引号。(双引号字段中的单引号将被忽略,反之亦然。)如果先行搜索成功,分号将作为分隔符

与匹配字段而不是分隔符的方法不同,此方法对空字段没有问题(甚至最后一个也没有问题:与许多其他
split
实现不同,Python不会自动丢弃尾随的空字段)。

下面是一种带注释的方法:

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
,您还可以获得对转义引号的支持

您还不清楚如何处理分号分隔符前后的前导空格,并且示例文本中的任何字段都没有前导空格;Bc“作为:


由于您没有'\n',请使用它替换任何不在引号字符串中的';'

>>> 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']
>a='a',B,C',D'
>>>a.拆分(“,”)
[A'、'B'、'C'、'D']
失败。现在尝试csv模块
>>>导入csv
>>>从StringIO导入StringIO
>>>数据=字符串IO(a)
>>>资料
>>>reader=csv.reader(数据,分隔符=',')
>>>对于读卡器中的行:打印行
... 
['A,“B,C”,D']

通用解决方案:

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", '']
此解决方案:

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))
  • 捕获所有空组(包括开头和结尾)
  • 适用于大多数常用的分隔符,包括空格、制表符和 逗号
    ['a', 'b', 'c']
    
    >>> 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']
    
    >>> 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']
    
    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", '']
    
    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
    
    # 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']
    
    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']