用于自定义序列类型的 Python re

Posted

技术标签:

【中文标题】用于自定义序列类型的 Python re【英文标题】:Python re for custom sequence type 【发布时间】:2019-08-28 23:10:16 【问题描述】:

我有一个自定义的类序列对象s,它继承collections.Sequence 并实现自定义__len____getitem__。它代表一大串字符串(> 4GB)并且是延迟加载的(我无法将所有内容都加载到内存中)。

我想对它进行 RE 匹配,re.compile('some-pattern').match(s),但它以 TypeError: expected string or buffer 失败。

实际上,模式不像'.*'那样需要加载整个s;通常需要前几十个字节才能匹配;但是,我不能事先知道确切的字节数,我希望保持通用,因此我不想做类似re.compile('some-pattern').match(s[:1000]) 的事情。

关于如何创建re 接受的类似str 的对象有什么建议吗?

以下代码说明了我的失败尝试。从str 继承也不起作用。

In [1]: import re, collections

In [2]: class MyStr(collections.Sequence):
    def __len__(self): return len('hello')
    def __getitem__(self, item): return 'hello'[item]
   ...:

In [3]: print(re.compile('h.*o').match(MyStr()))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-df08913b19d7> in <module>()
----> 1 print(re.compile('h.*o').match(MyStr()))

TypeError: expected string or buffer

如果字符串的大块来自单个大文件,那么我可以使用mmap,它应该可以工作。但是,我的情况更复杂。我有多个大文件,我mmaped 每个文件并有一个自定义类,它是它们的串联视图。我实际上想从视图中的任何给定位置开始执行 RE 匹配。我在原始问题中省略了这些细节,但我认为这可能对想了解我为什么有如此奇怪要求的人有所帮助。

【问题讨论】:

你也有磁盘限制吗?如果磁盘大小不是问题,您可以暂时将所有文件连接到一个文件中,然后mmap 进行搜索。 str 继承对我有用:class Class(str): pass \n c = Class('hello') \n re.compile('h.*o').match(c) 你能说明为什么它不适合你吗? @zwer,连接所有文件不是一个选项。太慢了。 @Sanyash,在您的代码中,它实质上是在构建之前将整个字符串加载到内存中。这不是我的选择。 在这种情况下,唯一真正的方法是不使用 python,而是使用像 catgrep 这样的命令行工具。那,或者以某种方式找到或保证永远不会有跨越文件边界的匹配项(可能是一个不包含换行符并且所有文件都必须以换行符终止的正则表达式)。 【参考方案1】:

没有可以实现的特殊方法可以让re.match() 接受您的自定义类,并且不需要您将所有数据读入内存。

这是因为目前没有特殊方法可以让您的自定义类充当buffer-protocol object。 re 方法只接受 str 字符串(确实实现了缓冲区协议)和 unicode 字符串(和子类,直接访问的数据,而不是通过 __unicode__)。 re 方法不接受任意序列,只有缓冲区协议可以让您避免一次性将整个内容读入内存。

与其尝试实现自定义对象,但是,如果您的数据完全存储在单个磁盘文件中(但太大而无法读入内存),您希望使用memory mapping。内存映射使用操作系统的虚拟内存设施来访问文件的某些部分作为内存部分。

虚拟内存子系统通过将大块内存(“页面”)放在硬盘上,让您的操作系统管理的内存比您的计算机以 RAM 形式物理可用的内存更多。在访问内存时,操作系统会不断将页面从磁盘交换到物理内存并再次交换回来。内存映射只是将此功能扩展到现有文件,从而可以将非常大的文件视为单个大字符串,操作系统将确保您尝试访问的部分在需要时在内存中可用。

在 Python 中,此功能可通过 mmap module 获得,并且内存映射文件实现了缓冲区协议。您可以将此类对象直接传递给re.match(),Python 和您的操作系统将协同工作以搜索文件中的数据以进行匹配。

因此,给定一个大文件 filename = '/path/to/largefile' 和正则表达式 pattern,这将在文件的开头搜索您的模式的匹配项:

import re
import mmap
import os

fd = os.open(filename, os.O_RDONLY)
mapped = mmap.mmap(fd, 0)
matched = re.match(pattern, mapped)

如果你有多个文件,你需要找到一种方法来连接它们。虚拟地,或物理地。如果您使用的是 Linux,则可以使用网络块设备虚拟连接文件,也可以使用 FUSE 虚拟文件系统。见A virtual file containing the concatenation of other files

【讨论】:

感谢您提供的信息。实际上,我在原始问题的最后一段中提到了 mmap,我确实需要能够在多个文件的串联上运行。我想确认一下,没有办法在纯python中实现缓冲区协议对象,对吧? @KanLi 缓冲区协议的问题在于它不是基于迭代的——它假设一些连续的内存块,您的数据以固定的步幅分散在其中。如果缓冲区允许多个内存块,那么您可以mmap 多个文件并将它们的内存区域包装在这样的超级缓冲区结构中。 @KanLi:缓冲协议专门处理连续内存;该协议定义了起始地址和长度(数据本身是一个可以是多维的数组,具有固定大小的结构化数据)。如果您在 Linux 上,您可以尝试使用 network block device 将多个文件视为一个文件。 @KanLi:如果所有结果都完全包含在单个文件中,我只需将它们全部映射并遍历所有 re.match。 @JürgenStrobel:不,你不能,因为字符串是一维数组(ndim 设置为 0),你发现只适用于多维数组(PIL 数组样式)。 re 模块在请求访问缓冲区时使用PyBUF_SIMPLE flag,要求将ndim 设置为0。参见_sre.c module source code。

以上是关于用于自定义序列类型的 Python re的主要内容,如果未能解决你的问题,请参考以下文章

第四章:Python-高级编程-自定义序列类

第四章:Python-高级编程-自定义序列类

Python进阶:自定义对象实现切片功能

使用 ORTools 实现自定义酸洗代码

使用ORTools实现自定义酸洗代码

@JsonSerialize