在Python中使用正则表达式进行标记化

时间:2013-03-07 19:59:13

标签: python regex parsing token tokenize

我尝试将"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 for s in result if s]来过滤它们,但在我知道原因之前我不习惯这样做。

那么,(1)为什么那些None和空字符串,(2)可以做得更好?

3 个答案:

答案 0 :(得分:3)

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()会更有效率,因为它一次只能生成一个项目。如果你可能在不经过整个清单的情况下纾困,情况尤其如此。

原则上 也可能带有负面的lookbehind断言,但在实践中我认为不可能创建一个足够灵活的 - 我试过r"(?<!<[^>]*)\s+"并得到了错误“后视需要固定宽度模式”,所以我想这是一个禁忌。文档证实了这一点 - 后向断言(正面和负面)都需要固定宽度。

这种方法的问题是,如果你期望嵌套尖括号 - 那么你将无法得到你所期望的。例如,解析ds<hai <bye> foo>sd将产生ds<hai <bye>作为一个标记。我认为这是正则表达式无法解决的问题类 - 您需要更接近正确解析器的东西。在纯Python中编写一个并不困难,它一次通过字符并计算括号的嵌套级别,但这将非常慢。取决于您是否可以确定在输入中只能看到一个嵌套级别。

答案 1 :(得分:1)

我得到了这个正则表达式:

ss = "spam bar ds<hai bye>sd baz eggs ZQ<boo <abv> foo>WX  "

reg = re.compile('(?:'
                     '\S*?'
                     '<'
                     '[^<>]*?'
                     '(?:<[^<>]*>[^<>]*)*'
                     '[^<>]*?'
                     '>'
                       ')?'
                 '\S+')

print reg.findall(ss)

结果

['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs',
 'ZQ<boo <abv> foo>WX']

编辑1

一个新的正则表达式,更准确,在Cartroo的评论之后:

import re

pat = ('(?<!\S)'  # absence of non-whitespace before

       '(?:'
           '[^\s<>]+'

           '|'  # OR

           '(?:[^\s<>]*)'
           '(?:'
               '<'
               '[^<>]*?'
               '(?:<[^<>]*?>[^<>]*)*'
               '[^<>]*?'
               '>'
               ')'
           '(?:[^\s<>]*)'
       ')'

       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)

ss = ("spam i>j bar ds<hai bye>sd baz eggs Z<boo <abv>"
      " foo>W ttt <two<;>*<:> three> ")
print '%s\n' % ss
print reg.findall(ss)

ss = "a<b<E1>c>d <b<E2>c>d <b<E3>c> a<<E4>c>d <<E5>>d 
   <<E6>> <<>>"
print '\n\n%s\n' % ss
print reg.findall(ss)

结果

spam i>j bar ds<hai bye>sd baz eggs Z<boo <abv> foo>W 
ttt <two<;>*<:> three> 

['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs', 
 'Z<boo <abv> foo>W', 'ttt', '<two<;>*<:> three>']


a<b<E1>c>d <b<E2>c>d <b<E3>c> a<<E4>c>d <<E5>>d <<E6>> <<>>

['a<b<E1>c>d', '<b<E2>c>d', '<b<E3>c>', 'a<<E4>c>d', '<<E5>>d',
 '<<E6>>', '<<>>']

上述字符串格式正确,结果一致 在非格式良好的文本(关于括号)上,它可能会产生不希望的结果:

ss = """A<B<C>D  
 E<F<G>H 
I<J>K> 
 L<<M>N
   O<P>>Q
 R<<S>    T<<>"""
print '\n\n%s\n' % ss
print reg.findall(ss)

结果

A<B<C>D  
 E<F<G>H 
I<J>K> 
 L<<M>N
   O<P>>Q
 R<<S>    T<<>

['E<F<G>H \nI<J>K>', 'L<<M>N\n   O<P>>Q']

那是因为'(?:<[^<>]*?>[^<>]*)*'末尾的明星。删除星形可以关闭此行为。这种行为使得使用正则表达式来分析Crtaroo所称的“复杂”文本变得困难。

编辑2

当我说结果'E<F<G>H \nI<J>K>''L<<M>N\n O<P>>Q'是不合需要的时候,并不意味着找到的匹配部分不尊重正则表达式的模式(它怎么可能?)制作它;确实很好地形成了匹配部分:
两个<G><J>部分位于两个括号< <G> <J> >之间 两个部分<M><P>位于两个括号< <M> <P> >

之间

事实上,这是一种轻描淡写,暗示找到的每个匹配部分应该只延伸一行。但是,只要表达了轻描淡写,就会出现一种可能的解决方案 如果不希望在几行上延伸匹配部分,则很容易告诉正则表达式与它们不匹配,这与我写的相反。只需在正则表达式模式中的某些位置添加字符\n即可。

实际上,这意味着匹配部分不得越过\n字符,然后可以将此字符视为匹配部分的分隔符。因此,可以想要任何其他字符作为同一行上存在的匹配部分之间的分隔符,例如以下代码中的#

正则表达式不能从学校烹饪或取出孩子,但它们非常强大。说正则表达式在畸形文本上的行为是一个问题太短:必须补充一点,这是文本的问题,而不是正则表达式。正则表达式按照命令执行的操作:吃任何给它的文本。并且它贪婪地吃它,也就是说,在没有验证它的任何一致性的情况下,它不是它的预期行为,然后如果它被提供了一个不明智的文本则不负责任。说正则表达式在畸形文本上的行为是一个问题听起来好像有人会责备一个孩子,有时候会用威士忌和胡椒粉来营养。

编码人员负责确保传递给正则表达式的文本格式正确。与编码器将验证片段放在代码中以确保条目是整数以便程序正确运行的方式相同。

当一个人试图解析标记的文本作为XML文本时,这一点与正则数据的滥用不同。正则表达式无法解析这样的文本,好吧,因为不可能制作一个正确对正确标记的文本作出正确反应的正则表达式。这也是编码员不负责任的责任 这并不意味着如果此文本已经过验证,则不得使用正则表达式来分析标记的文本。
无论如何,如果文本格式错误,即使解析器也不会捕获数据。

我的意思是我们必须区分:

  • 传递给正则表达式的文本的性质(格式错误/格式正确)

  • 使用正则表达式(解析/分析)时追求目标的本质

import re

ss = """
 A<:<11>:<12>:>
 fgh
 A<#:<33>:<34>:>
 A#<:<55>:<56>:>
 A<:<77>:<78> i<j>
 A<B<C>D #
 E<F<G>H #
 I<J>K> 
 L<<M>N 
 O<P>>Q  #
 R<<S>  T<<>"""
print '%s\n' % ss

pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>]*?'
               '(?:<[^<>]*?>[^<>]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '------------------------------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>\n]*?'
               '(?:<[^<>\n]*?>[^<>\n]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n----------- with \\n -------------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>#]*?'
               '(?:<[^<>#]*?>[^<>#]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n------------- with # -----------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>#]*)'
           '(?:<'
               '[^<>#]*?'
               '(?:<[^<>#]*?>[^<>#]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n------ with ^# everywhere -------'
print '\n'.join(map(repr,reg.findall(ss)))

结果

 A<:<11>:<12>:>
 fgh
 A<#:<33>:<34>:>
 A#<:<55>:<56>:>
 A<:<77>:<78> i<j>
 A<B<C>D #
 E<F<G>H #
 I<J>K> 
 L<<M>N 
 O<P>>Q  #
 R<<S>  T<<>

------------------------------
'A<:<11>:<12>:>'
'A<#:<33>:<34>:>'
'A#<:<55>:<56>:>'
'i<j>'
'E<F<G>H #\n I<J>K>'
'L<<M>N \n O<P>>Q'

----------- with \n -------------
'A<:<11>:<12>:>'
'A<#:<33>:<34>:>'
'A#<:<55>:<56>:>'
'i<j>'

------------- with # -----------
'A<:<11>:<12>:>'
'A#<:<55>:<56>:>'
'i<j>'
'L<<M>N \n O<P>>Q'

------ with ^# everywhere -------
'A<:<11>:<12>:>'
'i<j>'
'L<<M>N \n O<P>>Q'

答案 2 :(得分:0)

我认为None值是由于基于此文档中此行的模式中() s的存在所致:

  

如果在模式中使用捕获括号,则模式中所有组的文本也将作为结果列表的一部分返回

在输入上使用Regex Tester也可能有助于可视化解析:http://regexpal.com/ ?flags = g&amp; estgex =%28 \ S*%3C.*%3F%3E \ S *%29 | \ S%2B&安培;输入=垃圾%20巴%20DS%3Chai%20bye%3Esd%20baz%20eggs