我尝试标记像 "spam bar ds<hai bye>sd baz eggs"
这样的字符串进入列表['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs']
,即像 str.split()
但保留 < ... >
内的空格.
我的解决方案是使用re.split
与 (\S*<.*?>\S*)|\s+
图案。但是我得到以下信息:
>>> re.split('(\S*<.*?>\S*)|\s+', "spam bar ds<hai bye>sd baz eggs")
['spam', None, 'bar', None, '', 'ds<hai bye>sd', '', None, 'baz', None, 'eggs']
不确定那些None
在哪里s 和空字符串来自。当然,我可以使用列表理解过滤掉它们 [s for s in result if s]
,但在我知道原因之前我不愿意这样做。
那么,(1) 为什么是 None
s 和空字符串,(2) 可以做得更好吗?
最佳答案
None
和空字符串值是因为您在模式中使用了捕获括号,因此拆分包含匹配的文本 - 请参阅 official documentation提到这一点。
如果您将模式修改为 r"((?:\S*<.*?>\S*)|\S+")
(即转义括号以使其不捕获并将空格纠正为非空格)它应该可以工作,但只能通过保留分隔符来实现,然后您需要通过跳过替代项来过滤掉分隔符。我认为你这样做会更好:
ITEM_RE = re.compile(r"(?:\S*<.*?>\S*)|\S+")
ITEM_RE.findall("spam bar ds<hai bye>sd baz eggs")
如果您不需要实际的列表(即您一次只浏览一项),则 finditer()
效率更高,因为它一次只产生一个。如果您可能在不查看整个列表的情况下退出,则尤其如此。
原则上也有可能使用否定的后向断言,但实际上我认为不可能创建一个足够灵活的 - 我尝试过 r"(?<!<[^>]*)\s+"
并收到错误“后视需要固定宽度模式”,所以我想这是一个禁忌。文档证实了这一点 - 后向断言(正面和负面)都需要固定宽度。
这种方法的问题在于,如果您期望嵌套尖括号 - 那么您将无法得到您所期望的结果。例如解析ds<hai <bye> foo>sd
将产生ds<hai <bye>
作为一个 token 。我认为这是正则表达式无法解决的一类问题 - 您需要更接近正确解析器的东西。用纯 Python 编写一个每次遍历字符并计算括号嵌套级别的程序并不难,但这会很慢。取决于您是否可以确定您只会在输入中看到一层嵌套。
关于python - 在 Python 中使用正则表达式进行标记化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15280577/