为什么这个带有正则表达式的Python脚本会变慢?

时间:2015-10-30 18:36:15

标签: python regex

工作是逐行读取一个非常大的XML文件,并存储已经在字符串中读取的内容。当字符串包含标记“player”和“/ player”之间的完整记录时,此记录中xml标记的所有值都应作为制表符分隔行写入文本文件,并从已读取的块中删除记录。 / p>

在过程结束时,应打印未删除的部分(剩余部分),以检查是否所有记录都已正确处理,并且没有任何内容未经处理。

我已经在Perl中使用了这个代码并且它运行得很快,但我想切换到Python。

我目前使用的Python脚本非常慢。

Python速度慢,还是使用正则表达式做错了?

import re

fh=open("players_list_xml.xml")

outf=open("players.txt","w")

x=""

cnt=0

while(cnt<10000):

line=fh.readline().rstrip()

x+=line

mo=re.search(r"<player>(.*)</player>",x)

while(mo):

    cnt=cnt+1

    if((cnt%1000)==0):
        print("processing",cnt)

    x=re.sub(re.escape(mo.group()),"",x)

    print("\t".join(re.findall(r"<[a-z]+>([^<]+)<[^>]+>",mo.group(1))),file=outf)

    mo=re.search(r"<player>(.*)</player>",x)

print("remainder",x)

outf.close()

fh.close()

3 个答案:

答案 0 :(得分:1)

你的正则表达式很慢,因为你正在使用“贪婪”表达式“回溯”(this answer提供了一个简单的Python示例)。此外,如评论中所述,您应该使用XML解析器来解析XML。 Regex从未对XML(或HTML)非常有用。

试图解释为什么你的特定表达很慢......

假设您的XML中有三个<player>...</player>个元素。您的正则表达式将首先匹配第一个开始<player>标记(该部分没问题)。然后(因为你正在使用贪婪的匹配)它将跳到文档的末尾并开始向后工作(回溯),直到它与最后的结束</player>标记匹配。如果写的正则表达式写得不好,它就会停在那里(所有三个元素都会与它们之间的所有非玩家元素匹配)。但是,这种匹配显然是错误的,所以你做了一些改变。然后,新的正则表达式将继续前进,继续回溯直到它找到第一个结束</player>标记。然后它会继续回溯,直到确定开始标记和最近找到的结束标记之间没有其他</player>标记。然后它将为第二组标签重复该过程,并再次为第三组标签重复该过程。所有这些回溯都需要花费很多时间。这是一个相对较小的文件。在评论中,您提到您的文件包含“超过五十万条记录”。哎哟!我无法想象需要多长时间。而你实际上匹配所有元素,而不仅仅是“玩家”元素。然后,您正在针对每个元素运行第二个正则表达式,以检查它们是否是播放器元素。我永远不会想到这会很快。

为了避免所有回溯,你可以使用“nongreedy”或“懒惰”正则表达式。例如(大大简化了代码):

r"<player>(.*?)</player>"

请注意,?表示上一个模式(.*)不一致。在这个例子中,在找到第一个打开的<player>标记之后,它会继续向前移动文档(不跳到最后),直到它找到第一个结束</player>标记然后它将是确信模式已匹配并继续查找第二次出现(但只能在第一次出现结束后在文档中搜索)。

当然,非同意的表达会更快。根据我的经验,在*+匹配时,nongreedy几乎总是你想要的(除了极少数情况下你不这样做)。

也就是说,如前所述,XML解析器更适合解析XML。事实上,许多XML解析器提供某种流式API,允许您将文档分段输入,以避免一次将整个文档加载到内存中(正则表达式不提供此优势)。我从lxml开始,然后移动到一些内置解析器,如果C依赖项不适合你。

答案 1 :(得分:0)

使用XML解析器:

import xml.parsers.expat

cnt=0
state="idle"
current_key=""
current_value=""
fields=[]

def start_element(name, attrs):
    global state
    global current_key
    global current_value
    global fields
    if name=="player":
        state="player"
    elif state=="player":
        current_key=name

def end_element(name):
    global state
    global current_key
    global current_value
    global fields
    global cnt
    if state=="player":
        if name=="player":
            state="idle"
            line="\t".join(fields)
            print(line,file=outf)
            fields=[]
            cnt+=1
            if((cnt%10000)==0):
                print(cnt,"players processed")
        else:
            fields.append(current_value)
            current_key=""
            current_value=""

def char_data(data):
    global state
    global current_key
    global current_value
    if state=="player" and not current_key=="":
        current_value=data

p = xml.parsers.expat.ParserCreate()

p.StartElementHandler = start_element
p.EndElementHandler = end_element
p.CharacterDataHandler = char_data

fh=open("players_list_xml.xml")

outf=open("players.txt","w")

line=True

while((cnt<1000000) and line):

    line=fh.readline().rstrip()

    p.Parse(line)

outf.close()

fh.close()

这是相当多的代码。

至少这会从原始XML生成一个29MB的文本文件,这个大小似乎正确。

速度是合理的,虽然这是一个简单的版本,但需要对记录进行更多处理。

在一天结束时,似乎只有正则表达式的Perl脚本正在以专用XML解析器的速度运行,这是非常了不起的。

答案 2 :(得分:0)

正如其他人所说,正确答案是使用 XML 解析器来解析 XML。

关于为什么它比你的 perl 版本慢这么多的问题的答案是,由于某种原因,python 的正则表达式很慢,比处理相同表达式的 perl 慢得多。我经常发现使用正则表达式的代码在 perl 中的速度要快两倍多。