在括号环境中的逗号不进行分割,其余逗号均进行分割。

11

我希望能够在Python多行字符串中根据逗号进行拆分,但是要注意当逗号出现在括号表达式内部时不进行拆分。例如,给定字符串:

{J. Doe, R. Starr}, {Lorem
{i}psum dolor }, Dol. sit., am. et.

应该拆分为
['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']

这涉及到括号匹配,所以正则表达式可能并没有什么帮助。PyParsingcommaSeparatedList,它几乎满足我的需求,除了保护引用(")环境而不是以{}为分隔符的环境。

有任何提示吗?


据我所知,Python不支持在正则表达式中使用递归。仅供参考,使用PCRE可以完成此任务:(?'braces'\{(?:[^{}]++|\g<braces>)*\})(*SKIP)(*FAIL)|, - Lucas Trzesniewski
你所要求的并不是一件简单的事情......正则表达式并不能很好地帮助你,因为你需要一个具有内存的状态机来匹配封闭项(括号、引号等)。 - Joran Beasley
不能没有递归正则表达式(那些进行递归的)。我以为Python现在有一个新版本可以做到这一点。有趣的是,Perl来自Python,但现在已经远远超越了它。 - user557597
@sln:什么意思是“Perl来自Python”?当Guido开始考虑Python时,Perl已经存在了,并且在大多数人甚至听说Python之前就已经广泛使用,并且在Python的1.x /早期2.x阶段的发展中受到影响。特别是Python的re引擎直接基于Perl。而且我不确定能够在没有警告的情况下花费指数时间来处理正则表达式是否算作“超越它”。 - abarnert
@abarnert - 我认为Perl可能已经存在,但Perl采用了许多功能,这是我不知道的。我简要地阅读了Python beta网站上的一些细节,似乎有很多新的东西正在使用可用的语法结构中出现。 - user557597
显示剩余3条评论
3个回答

17
编写自己的自定义分割函数:
 input_string = """{J. Doe, R. Starr}, {Lorem
 {i}psum dolor }, Dol. sit., am. et."""


 expected = ['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']

 def split(s):
     parts = []
     bracket_level = 0
     current = []
     # trick to remove special-case of trailing chars
     for c in (s + ","):
         if c == "," and bracket_level == 0:
             parts.append("".join(current))
             current = []
         else:
             if c == "{":
                 bracket_level += 1
             elif c == "}":
                 bracket_level -= 1
             current.append(c)
     return parts

 assert split(input_string), expected

干得好... :) 据我所知,这是目前唯一正确的答案。 - Joran Beasley
很好,但这种实现的假设是字符串中没有“{”或“}”字符,这些字符可能不属于分组的一部分。 例如 “:-}” 如果这种可能性存在,就需要考虑如何处理它。 - Marcel Wilson

8
你可以在这种情况下使用 re.split
>>> from re import split
>>> data = '''\
... {J. Doe, R. Starr}, {Lorem
... {i}psum dolor }, Dol. sit., am. et.'''
>>> split(',\s*(?![^{}]*\})', data)
['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']
>>>

以下是Regex模式匹配的解释:
,       # Matches ,
\s*     # Matches zero or more whitespace characters
(?!     # Starts a negative look-ahead assertion
[^{}]*  # Matches zero or more characters that are not { or }
\}      # Matches }
)       # Closes the look-ahead assertion

2
这对于更复杂的嵌套括号示例不会失败吗?例如 "{J. Doe, R. Starr {x,{y}}}, {Lorem{i}psum dolor }, Dol. sit., am. et." - Alex Riley
1
@ajcr - 是的,它会失败。但这就是我为什么说“在这种情况下”的原因。我给出的模式并不是万无一失的,只能处理简单的字符串。具体来说,它适用于没有嵌套花括号和逗号的字符串,就像OP的例子一样。然而,如果OP正在处理像那样复杂的字符串,最好放弃正则表达式,改用解析器构建。 - user2555451
我认为你不能将这个泛化为任何层面的解决方案。 - user557597
1
如果您愿意接受一个快速的 hack,但不能处理更复杂的情况,为什么不选择最简单的方法呢?您实际上并不需要处理匹配的开放和关闭括号对;只需将开放括号和关闭括号都视为等效的替代“引号”字符,并像 PyParsing 或 csv 等工具一样跳过任何在“引号”内部的逗号即可。 - abarnert

3

Lucas Trzesniewski的评论实际上可以在Python中使用PyPi正则表达式模块(我只是用编号替换了命名组以使其更短):

>>> import regex
>>> r = regex.compile(r'({(?:[^{}]++|\g<1>)*})(*SKIP)(*FAIL)|\s*,\s*')
>>> s = """{J. Doe, R. Starr}, {Lorem
{i}psum dolor }, Dol. sit., am. et."""
>>> print(r.split(s))
['{J. Doe, R. Starr}', None, '{Lorem\n{i}psum dolor }', None, 'Dol. sit.', None, 'am. et.']

该模式 - ({(?:[^{}]++|\g<1>)*})(*SKIP)(*FAIL) - 匹配类似于{...{...{}...}...}的结构(其中{匹配{(?:[^{}]++|\g<1>)*匹配2种选择中的0个或多个:1)任何一个字符,除了{}之外的任何一个字符([^{}]++),2)与整个子模式({(?:[^{}]++|\g<1>)*})相匹配的文本)。(*SKIP)(*FAIL)动词使引擎忽略匹配值的整个部分,从而将索引移动到匹配的末尾并保持无返回内容(我们“跳过”了我们匹配的内容)。

\s*,\s*匹配由0个或多个空格包围的逗号。

出现None是因为第一分支中有一个捕获组,在第二分支匹配时为空。我们需要在第一个选择分支中使用一个捕获组进行递归。要删除空元素,请使用推导式:

>>> print([x for x in r.split(s) if x])
['{J. Doe, R. Starr}', '{Lorem\n{i}psum dolor }', 'Dol. sit.', 'am. et.']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接