7

Python 中的字符串是不可变的,并且支持缓冲区接口。.split()在使用切片或方法时,不返回新字符串,而是返回指向旧字符串部分的缓冲区可能是有效的。但是,每次都会构造一个新的字符串对象。为什么?我看到的唯一原因是它会使垃圾收集变得更加困难。

是的:在常规情况下,内存开销是线性的并且不明显。复制很快,分配也很快。但是在 Python 中已经做了太多的事情,所以也许这样的缓冲区值得付出努力?

编辑:

似乎以这种方式形成子字符串会使内存管理更加复杂。仅使用 20% 的任意字符串,而我们无法释放其余字符串的情况是一个简单的示例。我们可以改进内存分配器,这样它就可以部分地释放字符串,但可能这主要是一个反证。buffer无论如何,memoryview如果内存变得至关重要,则可以模拟所有标准功能。代码不会那么简洁,但必须放弃一些东西才能得到一些东西。

4

3 回答 3

3

底层字符串表示是null-terminated,即使它跟踪长度,因此您不能有一个引用不是后缀的子字符串的字符串对象。这已经限制了您的提案的有用性,因为它会增加很多复杂性来以不同的方式处理足够和不足够的内容(并且放弃空终止字符串会带来其他后果)。

允许引用字符串的子字符串意味着使大量垃圾收集和字符串处理复杂化。对于每个字符串,您必须跟踪有多少对象引用每个字符或每个索引范围。这意味着使许多struct字符串对象和处理它们的任何操作复杂化,这意味着可能很大,放慢速度。

添加一个事实,从 python3 字符串开始有 3 种不同的内部表示,事情会变得太混乱而无法维护,而且您的提议可能没有提供足够的好处来被接受。


这种“优化”的另一个问题是当你想释放“大字符串”时:

a = "Some string" * 10 ** 7
b = a[10000]
del a

在此操作之后,您将获得b阻止的子字符串a(一个巨大的字符串)被释放。当然,您可以复制小字符串,但如果b = a[:10000](或另一个大数字)怎么办?10000 个字符看起来像一个大字符串,应该使用优化来避免复制,但它会阻止释放兆字节的数据。垃圾收集器必须不断检查是否值得释放一个大字符串对象并进行复制,所有这些操作必须尽可能快,否则最终会降低时间性能。

99% 的情况下,程序中使用的字符串是“小”(最多 10k 个字符),因此复制非常快,而您提出的优化开始对真正的大字符串有效(例如,从大文本中获取大小为 100k 的子字符串) 并且对于非常小的字符串要慢得多,这是常见的情况,即应该优化的情况。


如果您认为重要,那么您可以自由地提出 PEP,展示一个实施以及由此产生的速度/内存使用变化。如果它真的值得付出努力,它可能会包含在未来版本的 python 中。

于 2013-08-04T11:30:45.123 回答
3

这就是切片的工作原理。切片始终执行浅拷贝,允许您执行以下操作

>>> x = [1,2,3]
>>> y = x[:]

现在可以对字符串进行例外处理,但这真的值得吗?Eric Lippert 在博客中谈到了他不为 .NET 做这件事的决定;我猜他的论点也适用于 Python。

另请参阅此问题

于 2013-08-04T10:45:48.343 回答
2

如果您担心内存(对于非常大的字符串),请使用buffer()

>>> a = "12345"
>>> b = buffer(a, 2, 2)
>>> b
<read-only buffer for 0xb734d120, size 2, offset 2 at 0xb734d4a0>
>>> print b
34
>>> print b[:]
34

了解这一点可以让您使用字符串方法的替代方法,例如split().

如果您想要split()一个字符串,但保留原始字符串对象(您可能需要它),您可以这样做:

def split_buf(s, needle):
    start = None
    add = len(needle)
    res = []
    while True:
        index = s.find(needle, start)
        if index < 0:
            break
        res.append(buffer(s, start, index-start))
        start = index + add
    return res

或者,使用.index()

def split_buf(s, needle):
    start = None
    add = len(needle)
    res = []
    try:
        while True:
            index = s.index(needle, start)
            res.append(buffer(s, start, index-start))
            start = index + add
    except ValueError:
        pass
    return res
于 2013-08-04T10:48:34.140 回答