【问题标题】:slices to immutable strings by reference and not copy通过引用而不是复制到不可变字符串的切片
【发布时间】:2012-04-22 13:20:05
【问题描述】:

如果您在 Python 字符串上使用 string.split(),它会返回一个字符串列表。这些被拆分出来的子字符串是它们的父字符串部分的副本。

是否有可能获得一些更便宜的切片对象,它只包含拆分出的位的引用、偏移量和长度?

是否有可能有一些“字符串视图”来提取和处理这些子字符串,就好像它们是字符串一样,而不复制它们的字节?

(我问,因为我有非常大的字符串,我想切片并且偶尔会耗尽内存;删除副本将是一个廉价的配置文件引导的胜利。)

【问题讨论】:

  • 以下使用 buffer() 的答案仅适用于 2.7。 memoryview() 不能与 unicode 字符串一起使用,这些字符串在 3.x 中是普通字符串。

标签: python


【解决方案1】:

buffer 会给你一个字符串的只读视图。

>>> s = 'abcdefghijklmnopqrstuvwxyz'
>>> b = buffer(s, 2, 10)
>>> b
<read-only buffer for 0x7f935ee75d70, size 10, offset 2 at 0x7f935ee5a8f0>
>>> b[:]
'cdefghijkl'

【讨论】:

  • 哇,我在想我知道所有的内置函数。直到。
  • 对此进行扩展,是否有标准的配方/模块用于拆分()到这些缓冲区中?
  • 不,但你可以适应one of these
  • @Will:请注意buffer() 已被memoryview() 取代。
【解决方案2】:

这是我想出的快速的类似字符串的缓冲区包装器;我能够使用它来代替经典字符串,而无需更改预期会使用字符串的代码。

class StringView:
    def __init__(self,s,start=0,size=sys.maxint):
        self.s, self.start, self.stop = s, start, min(start+size,len(s))
        self.size = self.stop - self.start
        self._buf = buffer(s,start,self.size)
    def find(self,sub,start=0,stop=None):
        assert start >= 0, start
        assert (stop is None) or (stop <= self.size), stop
        ofs = self.s.find(sub,self.start+start,self.stop if (stop is None) else (self.start+stop))
        if ofs != -1: ofs -= self.start
        return ofs
    def split(self,sep=None,maxsplit=sys.maxint):
        assert maxsplit > 0, maxsplit
        ret = []
        if sep is None: #whitespace logic
            pos = [self.start,self.start] # start and stop
            def eat(whitespace=False):
                while (pos[1] < self.stop) and (whitespace == (ord(self.s[pos[1]])<=32)):
                    pos[1] += 1
            def eat_whitespace():
                eat(True)
                pos[0] = pos[1]
            eat_whitespace()
            while pos[1] < self.stop:
                eat()
                ret.append(self.__class__(self.s,pos[0],pos[1]-pos[0]))
                eat_whitespace()
                if len(ret) == maxsplit:
                    ret.append(self.__class__(self.s,pos[1]))
                    break
        else:
            start = stop = 0
            while len(ret) < maxsplit:
                stop = self.find(sep,start)
                if -1 == stop:
                    break
                ret.append(self.__class__(self.s,self.start+start,stop-start))
                start = stop + len(sep)
            ret.append(self.__class__(self.s,self.start+start,self.size-start))
        return ret
    def split_str(self,sep=None,maxsplit=sys.maxint):
        "if you really want strings and not views"
        return [str(sub) for sub in self.split(sep,maxsplit)]
    def __cmp__(self,s):
        if isinstance(s,self.__class__):
            return cmp(self._buf,s._buf)
        assert isinstance(s,str), type(s)
        return cmp(self._buf,s)
    def __len__(self):
        return self.size
    def __str__(self):
        return str(self._buf)
    def __repr__(self):
        return "'%s'"%self._buf

if __name__=="__main__":
    test_str = " this: is: a: te:st str:ing :"
    test = Envelope.StringView(test_str)
    print "find('is')"
    print "\t",test_str.find("is")
    print "\t",test.find("is")
    print "find('is',4):"
    print "\t",test_str.find("is",4)
    print "\t",test.find("is",4)
    print "find('is',4,7):"
    print "\t",test_str.find("is",4,7)
    print "\t",test.find("is",4,7)
    print "split():"
    print "\t",test_str.split()
    print "\t",test.split()
    print "split(None,2):"
    print "\t",test_str.split(None,2)
    print "\t",test.split(None,2)
    print "split(':'):"
    print "\t",test_str.split(":")
    print "\t",test.split(":")
    print "split('x'):"
    print "\t",test_str.split("x")
    print "\t",test.split("x")
    print "''.split('x'):"
    print "\t","".split("x")
    print "\t",Envelope.StringView("").split("x")

【讨论】:

  • 您应该考虑将主要节写成真实的文档测试。
  • 在 32 位系统上,这个类的每个实例都将使用 232 字节的内存,在 64 位系统上会更多,所以这只值得花相当长的时间子串。您至少应该使用__slots__ 将每个实例消耗的内存减少到该数量的一半左右。
  • 为了节省更多内存,要么去掉缓冲区对象,要么去掉sstartstop。无论如何,摆脱size
  • 是的;我的字符串是 10MB+,而且我有很多;你会看到我尽可能多地使用 s 本身,希望它的 .find 等在 C 中
  • 删除缓冲区的使用应该允许此类的版本在 3.x 中工作。
【解决方案3】:

字符串对象在 Python 中总是指向一个以 NUL 结尾的缓冲区,因此必须复制子字符串。正如 Ignacio 指出的那样,您可以使用 buffer() 获取字符串内存的只读视图。不过,buffer() 内置函数已被更通用的 memoryview 对象所取代,这些对象在 Python 2.7 和 3.x 中可用(buffer() 在 Python 3.x 中已消失)。

s = "abcd" * 50
view = memoryview(s)
subview = view[10:20]
print subview.tobytes()

此代码打印

cdabcdabcd

只要您调用tobytes(),就会创建一个字符串的副本,但是在切片旧的buffer 对象时会发生同样的情况,就像在 Ignacio 的回答中一样。

【讨论】:

  • 是的,它是我非常想避免的副本;关于如何获得始终保持视图但像字符串一样工作的东西的想法?
  • @Will:Ignacio 的解决方案和这个解决方案,如果您只保留缓冲区/内存视图,请避免复制。如果您想将其用作字符串,则必须将其临时变成字符串并对其进行处理。正如我之前所说,Python 字符串缓冲区是 NUL 终止的,因此不可能只使用另一个字符串的一部分作为字符串缓冲区。
  • 我的意思是像鸭子一样嘎嘎叫;我在我的 StringView 中添加了“in”和迭代,它运行良好。真可惜它不是内置的。
猜你喜欢
  • 2019-10-15
  • 2013-05-08
  • 2013-09-13
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 2013-08-23
相关资源
最近更新 更多