为什么方法链接与每行中的方法会导致进程被终止或内存不足?

Ric*_*cky 3 python replace space-complexity

最近,我正在处理一个大文本文件(~10GB)并尝试替换Python中的一些字符。

我尝试了这两个版本:

f = open('myFile.txt', 'r')
filedata = f.read()
filedata = filedata.replace(',', ' ').replace('-', ' ').replace('_', ' ')
Run Code Online (Sandbox Code Playgroud)
f = open('myFile.txt', 'r')
filedata = f.read()
filedata = filedata.replace(',', ' ')
filedata = filedata.replace('-', ' ')
filedata = filedata.replace('_', ' ')
Run Code Online (Sandbox Code Playgroud)

当我尝试第一个时,该进程在替换方法期间被终止。然而,当我使用第二个时,该进程并没有被杀死。(截屏。)

>>> f = open('myFile.txt', 'r')
... filedata = f.read()
... filedata = filedata.replace(',', ' ').replace('-', ' ').replace('_', ' ')
Killed

>>> f = open('myFile.txt', 'r')
... filedata = f.read()
... filedata = filedata.replace(',', ' ')
... filedata = filedata.replace('-', ' ')
... filedata = filedata.replace('_', ' ')
... print("Success!")
Success!
Run Code Online (Sandbox Code Playgroud)

我认为时间和空间复杂度没有显着差异。有谁知道幕后发生了什么?

jua*_*aga 6

一般来说,这对于链式调用来说不是问题,但在这种情况下,这是因为您维护:

 filedata = f.read()
Run Code Online (Sandbox Code Playgroud)

原来的参考资料。

所以:

filedata = filedata.replace(',', ' ').replace('-', ' ').replace('_', ' ')
Run Code Online (Sandbox Code Playgroud)

str从文件中读取的原始内容必须与每个后续.replace结果一起保留在内存中,直到最后发生赋值,此时其引用计数最终达到 0。replace当操作不更改字符串的结果大小时,单个 将会需要两倍的内存,因为该方法同时使用对原始字符串和新字符串的引用。因此,在进行第二次替换时,内存中必须具有原始字符串、一次替换的字符串以及两次替换的新字符串。

另一方面,

filedata = filedata.replace(',', ' ')
filedata = filedata.replace('-', ' ')
filedata = filedata.replace('_', ' ')
Run Code Online (Sandbox Code Playgroud)

这里,每个步骤最多需要原始字符串内存量的 2 倍,因为赋值会导致原始字符串的引用计数在继续后续操作之前被垃圾收集.replace,而且重要的是,原始字符串不会保留在内存中。

如果我说的是真的,那么以下应该有效:

filedata = f.read().replace(',', ' ').replace('-', ' ').replace('_', ' ')
Run Code Online (Sandbox Code Playgroud)

但执行此操作的Pythonic方法是在这种情况下完全避免.replace,因为您正在执行多个、单个替换。

为此,您应该使用str.translate.

filedata = f.read()
table = {ord(','): ' ', ord('-'): ' ', ord('_'): ' '}
filedata = fildata.translate(table)
Run Code Online (Sandbox Code Playgroud)

以下是一些经验证据:

import tracemalloc

tracemalloc.start()

result = "abcdefghij"*1_000_000
result = (
    result.replace('a', '*')
          .replace('b', '*')
          .replace('c', '*')
)

size, peak = tracemalloc.get_traced_memory()
print(f"{size=}, {peak=}")

del result
tracemalloc.reset_peak()

result = "abcdefghij"*1_000_000
result = result.replace('a', '*')
result = result.replace('b', '*')
result = result.replace('c', '*')

size, peak = tracemalloc.get_traced_memory()
print(f"{size=}, {peak=}")

del result
tracemalloc.reset_peak()

result = ("abcdefghij"*1_000_000).replace('a', '*').replace('b', '*').replace('c', '*')

size, peak = tracemalloc.get_traced_memory()
print(f"{size=}, {peak=}")
Run Code Online (Sandbox Code Playgroud)

上面的输出是我所期望的:

size=10000625, peak=30000723
size=10000681, peak=20000730
size=10000681, peak=20000730
Run Code Online (Sandbox Code Playgroud)