首页 > 解决方案 > 字符串切片是否在内存中执行复制?

问题描述

我想知道是否:

a = "abcdef"
b = "def"
if a[3:] == b:
    print("something")

确实执行了a内存中某处的“def”部分的副本,或者是否就地完成了字母检查?

注意:我说的是字符串,而不是列表(我知道答案)

标签: pythonpython-3.x

解决方案


字符串切片在 CPython 中创建一个副本。

查看源代码,此操作在unicodeobject.c:unicode_subscript. 当 step 为 1、start 为 0 并且字符串的全部内容被切片时,显然存在重用内存的特殊情况 - 这进入unicode_result_unchanged并且不会有副本。但是,一般情况下PyUnicode_Substring,所有道路都通向memcpy.

要凭经验验证这些说法,您可以使用 stdlib 内存分析工具tracemalloc

# s.py
import tracemalloc

tracemalloc.start()
before = tracemalloc.take_snapshot()
a = "." * 7 * 1024**2  # 7 MB of .....   # line 6, first alloc
b = a[1:]                                # line 7, second alloc
after = tracemalloc.take_snapshot()

for stat in after.compare_to(before, 'lineno')[:2]:
    print(stat)

您应该会看到前两个统计信息输出,如下所示:

/tmp/s.py:6: size=7168 KiB (+7168 KiB), count=1 (+1), average=7168 KiB
/tmp/s.py:7: size=7168 KiB (+7168 KiB), count=1 (+1), average=7168 KiB

这个结果显示了两个7 meg 的分配,这是内存复制的有力证据,并且将指示这些分配的确切行号。

尝试将切片从更改b = a[1:]b = a[0:]查看整个字符串特殊情况的效果:现在应该只有一个大分配,并且sys.getrefcount(a)会增加一。

理论上,由于字符串是不可变的,因此实现可以将内存重新用于子字符串切片。这可能会使任何基于引用计数的垃圾收集过程复杂化,因此在实践中它可能不是一个有用的想法。考虑从大得多的字符串中取出一个小切片的情况——除非您在切片上实现某种子引用计数,否则在子字符串的生命周期结束之前,无法释放来自大得多的字符串的内存。

对于特别需要可以在不复制基础数据的情况下进行切片的标准类型的用户,有memoryview. 有关更多信息,请参阅Python 中的 memoryview 到底是什么。


推荐阅读