所以我经常按照这样的模式编写代码:
_list = list(range(10)) # Or whatever
_list = [some_function(x) for x in _list]
_list = [some_other_function(x) for x in _list]
等等
我现在在另一个问题上看到了一条评论,解释了这种方法如何每次都创建一个新的列表,最好对现有列表进行变异,如下所示:
^{pr2}$这是我第一次看到这个明确的建议,我想知道这意味着什么:
1)突变能节省记忆吗?大概在重新分配之后,对“旧”列表的引用会降到零,而“旧”列表将被忽略,但是在发生这种情况之前是否有一个延迟,在这种情况下,我使用重新分配而不是改变列表时,可能会使用比所需更多的内存?在
2)使用变异是否有计算成本?我怀疑,在适当的地方改变一些东西比创建一个新的列表并放弃旧的列表要昂贵得多?在
在安全方面,我写了一个脚本来测试:
def some_function(number: int):
return number*10
def main():
_list1 = list(range(10))
_list2 = list(range(10))
a = _list1
b = _list2
_list1 = [some_function(x) for x in _list1]
_list2[:] = [some_function(x) for x in _list2]
print(f"list a: {a}")
print(f"list b: {b}")
if __name__=="__main__":
main()
哪些输出:
list a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list b: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
因此,突变似乎有一个缺点,即更容易引起副作用。虽然这些可能是可取的。是否有政治公众人物讨论此安全问题,或其他最佳实践指南?在
谢谢。在
编辑:相互冲突的答案:所以更多的记忆测试 到目前为止,我收到了两个相互矛盾的答案。在评论中,jasonharper写到,等式的右手边不知道左手边,因此内存的使用不可能受到左边出现的内容的影响。然而,在答案中,马苏德写道,“当使用[重新分配]时,会创建两个新的和旧的具有两个不同身份和价值的清单。然后,旧的列表被垃圾回收。但是当容器发生变化时,每一个值都会被检索、在CPU中更改并逐个更新。这似乎表明,进行重新分配时内存开销很大。在
我决定尝试使用memory-profiler来深入挖掘。以下是测试脚本:
from memory_profiler import profile
def normalise_number(number: int):
return number%1000
def change_to_string(number: int):
return "Number as a string: " + str(number) + "something" * number
def average_word_length(string: str):
return len(string)/len(string.split())
@profile(precision=8)
def mutate_list(_list):
_list[:] = [normalise_number(x) for x in _list]
_list[:] = [change_to_string(x) for x in _list]
_list[:] = [average_word_length(x) for x in _list]
@profile(precision=8)
def replace_list(_list):
_list = [normalise_number(x) for x in _list]
_list = [change_to_string(x) for x in _list]
_list = [average_word_length(x) for x in _list]
return _list
def main():
_list1 = list(range(1000))
mutate_list(_list1)
_list2 = list(range(1000))
_list2 = replace_list(_list2)
if __name__ == "__main__":
main()
请注意,我知道,例如,这个查找平均字长函数不是特别好写。只是为了测试。在
结果如下:
Line # Mem usage Increment Line Contents
================================================
16 32.17968750 MiB 32.17968750 MiB @profile(precision=8)
17 def mutate_list(_list):
18 32.17968750 MiB 0.00000000 MiB _list[:] = [normalise_number(x) for x in _list]
19 39.01953125 MiB 0.25781250 MiB _list[:] = [change_to_string(x) for x in _list]
20 39.01953125 MiB 0.00000000 MiB _list[:] = [average_word_length(x) for x in _list]
Filename: temp2.py
Line # Mem usage Increment Line Contents
================================================
23 32.42187500 MiB 32.42187500 MiB @profile(precision=8)
24 def replace_list(_list):
25 32.42187500 MiB 0.00000000 MiB _list = [normalise_number(x) for x in _list]
26 39.11328125 MiB 0.25781250 MiB _list = [change_to_string(x) for x in _list]
27 39.11328125 MiB 0.00000000 MiB _list = [average_word_length(x) for x in _list]
28 32.46484375 MiB 0.00000000 MiB return _list
我发现,即使我把列表的大小增加到100000,重新分配仍然会占用更多的内存,但是,可能只多出1%。这使我认为额外的内存开销可能只是某个地方的一个额外指针,而不是整个列表的开销。在
为了进一步验证这个假设,我每隔0.00001秒执行基于时间的分析,并将结果绘制成图表。我想看看内存使用量是否会因为垃圾收集(引用计数)而立即消失。可惜,我还没找到这样的尖刺。在
有人能解释一下这些结果吗?到底是什么原因导致了内存使用量的持续增长?在
所有坐标都参考一个屏幕由像素和每英寸像素数确定的设备
我希望这能帮助你重新思考这个问题
相关问题 更多 >
编程相关推荐