有没有一种更优雅的方法可以将包含 mpz 值的 Textfile 读取到整数列表中?

Is there a more elegant way to read a Textfile containing mpz values into a list of integers?

提问人:Eldar Sultanow 提问时间:1/23/2022 最后编辑:Eldar Sultanow 更新时间:1/24/2022 访问量:145

问:

我有一个包含数字的文本文件,如下所示:

[mpz(0), mpz(0), mpz(0), mpz(0), mpz(4), mpz(54357303843626),...]

有没有一种简单的方法可以将其直接解析为整数列表?目标数据类型是 mpz 整数还是普通 python 整数都无关紧要。

到目前为止,我尝试过并且有效的是纯解析(注意:目标数组需要提前用零初始化,因为它可能大于文本文件中的列表):y_val3

text_file = open("../prod_sum_copy.txt", "r")
content = text_file.read()[1:-1]
text_file.close()
content_list = content.split(",")
y_val3 = [0]*10000
print(content_list)
for idx, str in enumerate(content_list):
    m = re.search('mpz\(([0-9]+)\)', str)
    y_val3[idx]=int(m.group(1))
print(y_val3)

尽管这种方法有效,但我不确定这是否是最佳实践,或者是否存在比普通解析更优雅的方法。

为了方便起见:这是 GitHub 上的原始文本文件。注意:此文本文件可能会增长,从而在性能和可伸缩性等方面发挥作用。

Python 解析 多精度 GMPY

评论


答:

2赞 E. Ducateme 1/23/2022 #1

我试着从人类可读的角度和性能的角度来看一个更优雅的解决方案。

警告:

  • 这里发生了很多事情
  • 我没有原始文件,因此下面的数字与您在设备上可能获得的任何数字都不匹配
  • 有太多的工作需要尝试对所有不同的部分进行基准测试,所以我试图专注于几个最大的组件

下面的突破和时间似乎显示了几种方法的一个数量级差异,因此它们可能仍然可用于衡量计算工作量的水平。

我的第一种方法是尝试测量文件读/写添加到进程中的开销量,以便我们可以探索仅在数据处理步骤上集中了多少计算工作。

为此,我创建了一个函数,其中包括文件读取和测量整个过程,端到端,以查看我的迷你示例文件花费了多长时间。我在 Jupyter 笔记本中使用了这个。%timeit

然后,我将文件读取步骤分解为它自己的函数,然后仅用于数据处理步骤,以帮助向我们展示:%timeit

  • 在原始方法中,文件读取与数据处理使用了多少时间
  • 在改进的方法中,数据处理方法使用了多少时间。

原始方法(在函数中)

import re

def original():
    text_file = open("../prod_sum_copy.txt", "r")
    content = text_file.read()[1:-1]
    text_file.close()
    content_list = content.split(",")

    y_val3 = [0]*10000

    for idx, element in enumerate(content_list):
        m = re.search('mpz\(([0-9]+)\)', element)
        y_val3[idx]=int(m.group(1))
    return y_val3

我假设,对于我非常短的示例数据,处理时间的很大一部分只是用于打开磁盘上的文件、将数据读入内存、关闭文件等的时间。

%timeit original()
140 µs ± 10.2 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

将读文件与数据处理方法分开

此方法包括对文件读取过程的微小改进。计时测试不包括文件读取过程,因此我们不知道微小的更改对整个过程的影响有多大。作为记录,我通过将读取过程封装在上下文管理器中(在后台处理关闭)来消除对该方法的手动调用,因为这是读取文件的 Python 最佳实践。.close()with

import re

def read_filea():
    with open("../prod_sum_copy.txt", "r") as text_file:
        content = text_file.read()[1:-1]
        return content

content = read_filea()
print(content)
def a():
    y_val3 = [0]*10000
    content_list = content.split(",")
    for idx, element in enumerate(content_list):
        m = re.search('mpz\(([0-9]+)\)', element)
        y_val3[idx]=int(m.group(1))
    return y_val3

通过仅对数据处理部分进行计时,我们发现,我们预测的文件读取 (IO) 在这个简单的测试用例中发挥了重要作用。它还为我们提供了一个想法,即我们应该为数据处理部分花费多少时间。让我们看看另一种方法,看看我们是否可以将时间缩短一点。

%timeit read_filea()
21.5 µs ± 185 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

简化的数据处理方法(和单独的读取文件)

在这里,我们将尝试使用一些 Python 最佳实践或 Python 工具来减少总体时间,包括:

  • 列表推导
  • 使用该方法来消除对函数的一些直接和重复调用以及对该方法的直接和重复调用(注意:findall 可能会在后台执行一些操作,老实说,我不知道我们避免它是否会有好处)。但是我发现这种方法的可读性高于原始方法。re.findall()re.search()m.group()

让我们看一下代码:

import re

def read_fileb():
    with open("../prod_sum_copy.txt", "r") as text_file:
        content = text_file.read()[1:-1]
    return content

content = read_fileb()

def b():
    y_val3 = [int(element) for element in re.findall(r'mpz\(([0-9]+)\)', content)]
    return y_val3

这种方法的数据处理部分比原始方法中的数据处理步骤快约 10 倍。

%timeit b()
2.89 µs ± 210 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)  

评论

1赞 Eldar Sultanow 1/23/2022
谢谢你的精彩解释!为了使事情可重现,我将原始文本文件添加到我的帖子中。将来,此 Texfile 可能会显著增长,这使得您的性能考虑非常重要。
0赞 Arty 1/24/2022
@EDucateme 很好的答案!赞成投票。也许您可以帮助我们投票重新讨论这个问题,Eldar Sultanow 也提出了相关问题。它被意外关闭,并且已经由两个人投票支持重新开放,我们需要第三个。
0赞 Arty 1/24/2022 #2

有一个聪明的技巧如何将数据从 Python 格式打印回原始对象。只是做,完整的例子如下。obj = eval(string)

您可以将此 eval 解决方案用于几乎任何 python 对象,甚至是通过或类似方式打印到文件的复杂对象。基本上,有效的 python 代码可以通过 .print(python_object)eval()

eval() 允许完全不使用任何字符串处理/解析函数,没有正则表达式或其他任何函数。

请注意,eval() 不会检查它运行的字符串,因此如果其中的字符串来自未知来源,则可能包含恶意代码,此代码可以对您的 PC 执行任何操作,因此 eval() 仅使用受信任的代码字符串。

下面的代码使用了带有示例文件内容的字符串。我使用字符串而不是文件作为示例,因此我的代码可以完全由 StackOverflow 访问者运行,没有依赖关系。如果只读打开的文件,您只需将其替换为,仅此而已,代码就可以工作了。textffor line in text.split('\n'):for line in f:

在线试用!

from gmpy2 import mpz

text = '''
[mpz(12), mpz(34), mpz(56)]
[mpz(78), mpz(90), mpz(21)]
'''

nums = []
for line in text.split('\n'):
    if not line.strip():
        continue
    nums.append(eval(line))

print(nums)

输出:

[[mpz(12), mpz(34), mpz(56)], [mpz(78), mpz(90), mpz(21)]]