以上下文敏感的方式通过分隔符拆分字符串

时间:2009-06-19 19:51:33

标签: python regex split

例如,我想拆分

str = '"a,b,c",d,e,f'

进入

["a,b,c",'d','e','f']

(即不拆分引用的部分)在这种情况下,可以用

完成
re.findall('".*?"|[^,]+',str)

但是,如果

str = '"a,,b,c",d,,f'

我想要

["a,,b,c",'d','','f']

即。我想要一个类似python的split函数的行为。有没有什么方法可以在一个(小)行中做到这一点,可能使用Python的re库?

实际上,我刚刚意识到(在这个网站上)csv模块非常适合我想做的事情,但我很好奇是否有一个正则表达式可以用来做它。

7 个答案:

答案 0 :(得分:2)

使用csv模块,因为它是一个真正的解析器。对于涉及规则改变的匹配分隔符的大多数事情,正则表达式是非最优的(或完全不适合的)(我不确定这个特定的语法是否规则)。您可能能够创建一个在这种情况下可以工作的正则表达式,但它会相当复杂(特别是处理像“他说,”这样的情况,你是怎么回事。)

答案 1 :(得分:2)

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

匹配逗号后,如果前面有一定数量的引号,则逗号必须在一对引号内,因此不算作分隔符。显然,这并不考虑转义引号的可能性,但如果需要可以处理 - 它只是使正则表达式大约是现有的两倍。 :d

答案 2 :(得分:1)

另一方面,为此编写状态机似乎非常简单。 DFA和正则表达式具有相同的功能,但通常其中一个更适合于手头的问题,并且通常非常依赖于您可能需要实现的其他逻辑。

答案 3 :(得分:1)

Friedl的掌握正则表达式

Page 271有一个正则表达式,用于提取可能引用的CSV字段,但需要进行一些后处理:

>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))',str)
[('a,b,c', ''), ('', 'd'), ('', 'e'), ('', 'f')]
>>> re.findall('(?:^|,)(?:"((?:[^"]|"")*)"|([^",]*))','"a,b,c",d,,f')
[('a,b,c', ''), ('', 'd'), ('', ''), ('', 'f')]

与详细标志相同的模式:

csv = re.compile(r"""
    (?:^|,)
    (?: # now match either a double-quoted field
        # (inside, paired double quotes are allowed)...
        " # (double-quoted field's opening quote)
          (    (?: [^"] | "" )*    )
        " # (double-quoted field's closing quote)
    |
      # ...or some non-quote/non-comma text...
        ( [^",]* )
    )""", re.X)

答案 4 :(得分:0)

你可以使用非贪婪的说明符来接近。我最接近的是:

>>> re.findall('(".*?"|.*?)(?:,|$)',  '"a,b,c",d,e,f')
['"a,,b,c"', 'd', '', 'f', '']

但是正如你所看到的那样,你最后会得到一个冗余的空字符串,这与字符串以逗号结尾时得到的结果无法区分:

>>> re.findall('(".*?"|.*?)(?:,|$)', '"a,b,c",d,e,f,')
['"a,,b,c"', 'd', '', 'f', '']

所以你需要在最后做一些手动调整 - 比如:

matches = regex,findall(s)
if not s.endswith(","): matches.pop()

matches = regex.findall(s+",")[:-1]

可能有更好的方法。

答案 5 :(得分:0)

这是一个完成任务的功能:

def smart_split(data, delimiter=","):
    """ Performs splitting with string preservation. This reads both single and
        double quoted strings.
    """
    result = []
    quote_type = None
    buffer = ""
    position = 0
    while position < len(data):
        if data[position] in ["\"", "'"]:
            quote_type = data[position]
            while quote_type is not None:
                position += 1
                if data[position] == quote_type:
                    quote_type = None
                    position += 1
                else:
                    buffer += data[position]
        if data[position] == delimiter:
            result.append(buffer)
            buffer = ""
        else:
            buffer += data[position]
        position += 1
    result.append(buffer)
    return result

使用示例:

str = '"a,b,c",d,e,f'
print smart_split(str)
# Prints: ['a,b,c', 'd', 'e', 'f']

答案 6 :(得分:0)

这是一个非常简短的功能,可以做同样的事情:

def split (aString):
    splitByQuotes = (",%s,"%aString).split('"')
    splitByQuotes[0::2] = [x.split(",")[1:-1] for x in splitByQuotes[0::2]]
    return [a.strip() \
        for b in splitByQuotes \
        for a in (b if type(b)==list else [b])]

它将引号所在的字符串拆分,创建一个列表,其中每个偶数元素都是引号之外的东西,每个奇数元素都是封装在引号内的东西。它单独留下的引号中的东西,它外面的东西在逗号所在的地方分开。现在我们有一个交替列表和字符串的列表,然后我们用最后一行解包。在开头用逗号包装字符串并在中间删除逗号的原因是为了防止列表中的备用空元素。它应该能够处理空格 - 我在末尾添加了一个strip()函数以使其产生干净的输出,但这不是必需的。

用法:

>>> print split('c, , "a,,b,c",d,"moo","f"')
['c', '', 'a,,b,c', 'd', 'moo', 'f']