我有一个包含数字的文本文件,如下所示:
[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上的原始文本文件。注意:这个文本文件可能会随着时间的推移而增长,这会带来性能和可伸缩性等方面的影响。
我试着从可读性和性能的角度来看一个更优雅的解决方案。
注意事项:
- 这里发生了很多事情
- 我没有原始文件,所以下面的数字与您在设备上获得的任何数字都不匹配
- 有太多的工作要尝试并对所有不同的部分进行基准测试,所以我试图专注于几个最大的组件
下面的突破和时间似乎显示了几种方法的数量级差异,因此它们可能仍然可以用于衡量计算工作的水平。
我的第一种方法是尝试测量文件读/写添加到过程中的开销,这样我们就可以探索有多少计算工作只集中在数据处理步骤上。
为了做到这一点,我制作了一个函数,其中包括文件的读取和测量整个过程,端到端查看我的迷你示例文件花了多长时间。我用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)
从数据处理方法中分离Readfile
这种方法包括对文件读取过程的微小改进。计时测试不包括文件读取过程,所以我们不知道这个小变化对整个过程的影响有多大。为了记录在案,我通过将读取过程封装在with
上下文管理器(在后台处理关闭)中,消除了对.close()
方法的手动调用,因为这是Python读取文件的最佳实践。
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)
简化的数据处理方法(和单独的Readfile)
在这里,我们将尝试使用一些Python最佳实践或Python工具来减少总体时间,包括:
- 列表理解
- 使用
re.findall()
方法来消除对re.search()
函数的一些直接和重复调用,以及对m.group()
方法的一些直接或重复调用(注意:findall可能是在后台进行的,我真的不知道我们避免它是否会有好处)。但我发现这种方法的可读性比原来的方法更高
让我们看看代码:
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)
有一个聪明的技巧,可以将Python格式打印的数据转换回原始对象。只需执行obj = eval(string)
,完整示例如下。
您可以将此eval解决方案用于几乎任何python对象,甚至是通过print(python_object)
或类似方式打印到文件中的复杂对象。基本上,任何一个有效的python代码都可以通过eval()
从字符串转换。
eval()允许完全不使用任何字符串处理/解析函数,不使用正则表达式或其他函数。
请注意,eval()不会检查它运行的字符串,所以如果它来自未知来源,里面的字符串可能会有恶意代码,这些代码可能会对你的电脑造成任何影响,所以只使用受信任的代码字符串执行eval(。
下面的代码使用了带有示例文件内容的text
字符串。我使用字符串而不是文件作为示例,这样我的代码就可以完全由StackOverflow访问者运行,而不需要依赖项。在只读打开文件f
的情况下,只需将for 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)]]