寻求使用python在一个大文件中进行正则表达式
Posted
技术标签:
【中文标题】寻求使用python在一个大文件中进行正则表达式【英文标题】:seek to regex in a large file using python 【发布时间】:2012-09-18 14:47:21 【问题描述】:我正在尝试在文件中寻找标记 ':path,',然后将以下所有(任意位数计数)数字读取为数字(因此对于 ':path,123' 我在文件中寻找然后读取整数 123)。然后读取当前搜索位置和 pos+123 之间的字符(将它们存储在列表或其他任何内容中)。然后寻找 ':path' 的下一个匹配项,然后重复该过程。
我想要一个有点像的函数:
def fregseek(文件,current_seek,/regex/): . . value_found = ? # 在 :path,[0-9]+ 之后读取下一个 N 字符的结果 . . 返回 next_start_seek, value_found一行中可能有任意数量的 ':path,' 匹配,并且该字符串可能出现在 ',' 之后指定的字符数内。我写了一堆乱七八糟的垃圾,每行都读到,然后每行 chomps 匹配所指示的前 N 个字符,然后继续处理字符串,直到它被全部吃完。然后读取下一个字符串,依此类推。
这太可怕了,当我真正需要做的就是寻找时,我不想从一个可能很大的文件中删除所有行(特别是因为换行符无关紧要,所以有一个额外的处理步骤只是因为行很容易从文件中提取是荒谬的)。
所以,这就是我想要解决的问题。我需要寻找一个匹配项,读取一个值,从该值的末尾继续寻找下一个匹配项,依此类推,直到文件用完。
如果有人可以帮助我,我会很高兴收到他们的来信:)
如果可能,我想避免使用非标准库,我也想要最短的代码,但这是我最不关心的问题(速度和内存消耗是重要因素,但我不想要 50 loc引导一些带有小功能的库,只要我知道它是什么,我就可以撕掉它。
我更喜欢 python 代码,但是,如果 perl 在这方面胜过 python,我将使用 perl,我也愿意接受聪明的 sed/awk/bash 脚本等,只要它们不是非常慢。
非常感谢。
【问题讨论】:
是否需要使用正则表达式?如果你只是想寻找像 ":path" 这样的标记,那是不必要的,如果你只是进行字符串搜索,它会更容易(也更有效)。 另外,你一直在谈论寻找,但是如果不扫描所有字节就无法进行搜索,而且我没有看到任何你不能一次性完成的事情,所以我'我不知道你为什么需要任何寻求。 感谢 cmets abarnert。如果我不必一次读入整个文件,则字符串搜索很好,但我必须有效地处理我读入的任何块。我不确定是否有比全部读入更好的方法,虽然我希望能够处理任意大的文件。理想情况下,我有几个基准测试选项,但现在我只有我糟糕的代码,已经有一个比我现在更好的答案:) 好的,你能一次映射整个文件吗(让操作系统担心磁盘I/O)?如果您不知道答案,如果您的文件 str/bytes
相同的代码,但改用mmap
对象。
通常我可以一次映射整个文件,但有时文件超过 4gb,如果可能的话,我希望不将自己限制在 64 位机器上。我也不确定替代方法的基准(例如一次阅读部分)是否会更好,但我肯定想看看人们提出了什么算法。未能采用更通用的方法,我喜欢 BrtH 提出的方法,我认为它是解决我问题的优雅方法,即使不完全符合我的要求。
【参考方案1】:
如果您不需要正则表达式,只需查找和切片即可。
无论哪种方式,简单的解决方案是将整个文件读入内存,然后找到并切片生成的str
/bytes
对象。
但如果您不能(或不想)将整个文件读入内存,这将不起作用。
幸运的是,如果您可以指望您的文件 mmap 文件放入内存中。 mmap
对象具有与字符串相同的方法的子集,因此您可以假装您有一个字符串,就像您将整个文件读入内存一样,但您可以依靠 Python 实现和操作系统让它以合理的效率工作。
根据您的 Python 版本,re
可能无法像扫描字符串一样扫描 mmap,它可能工作但速度很慢,或者工作得很好。所以,你不妨先尝试一下,如果它没有抛出异常或者比你预期的慢很多,你就完成了:
def findpaths(fname):
with open(fname, 'rb') as f:
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
for match in re.finditer(':path,([0-9]+)', m):
yield m[match.end():match.end()+int(match.group(1))]
(这与 BrtH 的答案相同,只是使用 mmap 而不是字符串,并重组为生成器而不是列表 - 尽管当然你可以通过用括号替换他的方括号来完成后一部分。)
如果您使用的是不能(有效地)re
和 mmap
的旧版(或非 CPython?)Python,那就有点复杂了:
def nextdigits(s, start):
return ''.join(itertools.takewhile(str.isdigit,
itertools.islice(s, start, None)))
def findpaths(fname):
with open(fname, 'rb') as f:
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
i = 0
while True:
n = m.find(':path', i)
if n == -1: return
countstr = nextdigits(m, n+6)
count = int(countstr)
n += 6 + len(countstr)
yield m[n:n+count]
i = n + 6 + count
这可能不是编写nextdigits
函数的最快方法。我不确定这实际上是否重要(计时并查看),但如果确实如此,其他可能性是切出m[n+6:n+A_BIG_ENOUGH_NUMBER]
并对其进行正则表达式,或者编写一个自定义循环,或者......另一方面,如果那是你的瓶颈,您可能会通过切换到具有 JIT(PyPy、Jython 或 IronPython)的解释器获得更多好处……
对于我的测试,我将事情分开:findpaths
接受一个类似字符串的对象,调用者执行with open
和mmap
位并将m
传递给findpaths
;我在这里不是为了简洁。
无论如何,我已经根据以下数据测试了这两个版本:
BLAH:path,3abcBLAH:path,10abcdefghijklmnBLAH:path,3abc:path,0:path,3abc
输出是:
abc
abcdefghij
abc
abc
我认为这是正确的?
如果我的早期版本导致它以 100% CPU 旋转,我的猜测是我没有在循环中正确增加 i
;这是您在紧密的解析循环中获得该行为的最常见原因。无论如何,如果您可以使用当前版本重现该内容,请发布数据。
【讨论】:
感谢您的建议,我喜欢退回发电机的想法。它对我来说不太有效,由于某种原因,当我实际尝试使用返回的生成器时,我要么得到非常快速的执行而没有任何事情发生,要么咀嚼我所有的系统资源并需要杀死(使用非常小的测试文件)。你能告诉我你是怎么用的吗? 这是一个很好的答案,可能比我的更符合要求,所以 +1。 但有一件事我不明白。您似乎假设计数是已知的并且是恒定的。但是,如果我正确理解了这个问题,情况并非如此,您还必须找到计数。除非计数始终为三位数,否则您必须使用正则表达式找到它。而且我认为你可以使用i = n + 7
,因为:path,at least one digit
这个词不能重叠。
@BrtH:在这两个方面都是正确的。您不必必须 使用正则表达式来读取计数,但它绝对更简单,而且可能更有效。即使您不能一开始就对 mmap 进行正则表达式,也有可能像 m[n+6:n+50] 这样的小切片正则表达式是最好的解决方案。
非常感谢您的回答 abarnert,非常全面且解释清楚。现在我可以继续有效地解析 mitmproxy 流文件(这本身就是我当前项目的一小部分)【参考方案2】:
在python中几乎一行就可以做到:
with open('filename.txt') as f:
text = f.read()
results = [text[i[0]:i[0] + i[1]] for i in
((m.end(), int(m.group(1))) for m in
re.finditer(':path,([0-9]+)', text))]
注意:未经测试...
【讨论】:
这对我来说非常适合小文件,非常感谢!我投了赞成票,因为这是一个很好的答案,而且比我的答案更有效率。我正在等待一个不需要一次读取整个文件的答案,但可以处理任意大的文件(也许使用 mmap?)。如果我在这里找不到它,我会接受你的,因为它帮助我继续处理更广泛的文件,尽管没有达到我的要求的顶峰(任意文件大小,几乎没有额外的成本......不小命令!)。再次感谢您的贡献:)以上是关于寻求使用python在一个大文件中进行正则表达式的主要内容,如果未能解决你的问题,请参考以下文章