Python中的可变字符串

53

你知道是否有一个提供可变字符串的Python库吗?Google返回的结果令人惊讶地很少。我找到的唯一可用的库是http://code.google.com/p/gapbuffer/,但它是用C编写的,我更希望它是用纯Python编写的。

编辑:感谢回复,但我需要一个高效的库。也就是说,''.join(list)可能有效,但我希望能够更加优化。此外,它必须支持常规字符串所支持的内容,例如正则表达式和unicode。


9
清单很适合这个目的。 - Aaron Yodaiken
5
你能否解释一下,为什么需要可变字符串?它的使用场景是什么? - Zaur Nasibov
2
@BasicWolf可能是为了在字符串内存不足时进行字符的高效替换而存在?我们避免创建字符串的副本。 - chuwy
5
嗯,对于这些目的,可以使用bytearray。在Python中,字符串并不是"内存高效"序列,而是并发高效的。考虑以下情况:您始终可以确定,无论原始字符串上的任何字符串修改操作如何,都不会影响它。因此,不存在并发、线程安全等问题。 - Zaur Nasibov
@BasicWolf 这将是最受欢迎的答案的合适补充。 - chuwy
显示剩余2条评论
8个回答

30

在Python中,可变序列类型为bytearray,请参见此链接


我不确定@Marcin指的是什么,因为bytearray允许你给bytearray的一个切片分配一个新值。 - jonathanrocher
@jonathanrocher 请查看编辑历史。Marcin指出了一个错误,并已经进行了更正。 - leewz
1
这应该是“正确”的答案。当前得票最高的涉及太多混乱。 - robert
29
bytearray 显然如其名,是一个字节数组。字符串不是字节序列,而是字节组的序列。也就是说,这仅适用于 ASCII 字符串,对于 Unicode 不适用。-1。 - freakish
1
注意多字节字符。例如:bytearray('aé'.encode('utf8')) bytearray(b'a\xc3\xa9') - Michael Grazebrook
显示剩余2条评论

23

这将使您能够高效地更改字符串中的字符,尽管您无法更改字符串的长度。

>>> import ctypes

>>> a = 'abcdefghijklmn'
>>> mutable = ctypes.create_string_buffer(a)
>>> mutable[5:10] = ''.join( reversed(list(mutable[5:10].upper())) )
>>> a = mutable.value
>>> print `a, type(a)`
('abcdeJIHGFklmn', <type 'str'>)

6
请注意,缓冲区在报告其len()时包括终止符。这会破坏具有负索引的切片,除非您对每个负索引都添加一个额外的-1。(对于Unicode缓冲区也是如此,因为这些类型的len和切片索引以字符为单位。) - ivan_pozdeev
1
注意:在Python3中,ctypes.create_string_buffer()函数的参数应为bytes类型,而ctypes.create_unicode_buffer()函数的参数应为字符串类型。 - Rustam A.

15
class MutableString(object):
    def __init__(self, data):
        self.data = list(data)
    def __repr__(self):
        return "".join(self.data)
    def __setitem__(self, index, value):
        self.data[index] = value
    def __getitem__(self, index):
        if type(index) == slice:
            return "".join(self.data[index])
        return self.data[index]
    def __delitem__(self, index):
        del self.data[index]
    def __add__(self, other):
        self.data.extend(list(other))
    def __len__(self):
        return len(self.data)

还有其他方法,比如可以创建StringIO、buffer或bytearray的子类。


为了能够使用正则表达式和字符串方法,如find,您需要从object而不是str进行子类化。 - chtenb
1
更正:正则表达式和 find 只能在原始字符串上工作。通过 __setitem__ 进行的修改将被忽略。是否有办法在可变字符串上使用正则表达式? - chtenb
你可以使用 re.match(expression, repr(mutable_string)) - Joel Cornett
6
但是你也可以使用一个普通的字符串。我想/需要利用可变性。 - chtenb
有太多的函数需要覆盖。而且你还需要检查各个Python版本之间的str API是否有任何差异。 - toolforger

3
如何简单地对list(Python中可变性的主要示例)进行子类化?
class CharList(list):

    def __init__(self, s):
        list.__init__(self, s)

    @property
    def list(self):
        return list(self)

    @property
    def string(self):
        return "".join(self)

    def __setitem__(self, key, value):
        if isinstance(key, int) and len(value) != 1:
            cls = type(self).__name__
            raise ValueError("attempt to assign sequence of size {} to {} item of size 1".format(len(value), cls))
        super(CharList, self).__setitem__(key, value)

    def __str__(self):
        return self.string

    def __repr__(self):
        cls = type(self).__name__
        return "{}(\'{}\')".format(cls, self.string)

这仅在您想打印或主动请求字符串表示时将列表连接回字符串。变异和扩展很简单,用户已经知道如何做了,因为它只是一个列表。
示例用法:
s = "te_st"
c = CharList(s)
c[1:3] = "oa"
c += "er"
print c # prints "toaster"
print c.list # prints ['t', 'o', 'a', 's', 't', 'e', 'r']

以下内容已经固定,详见下面的更新。
有一个(可以解决的)警告:目前还没有检查每个元素是否确实是字符。它至少会在除字符串外的所有情况下失败打印。但是,这些可以连接并可能导致像下面的代码示例中的奇怪情况:
使用自定义的 __setitem__,将长度不等于1的字符串分配给 CharList 项将引发 ValueError。其他所有内容仍然可以自由分配,但在打印时会引发 TypeError: sequence item n: expected string, X found,原因是 string.join() 操作。如果这还不够好,可以轻松添加进一步的检查(可能也适用于 __setslice__ 或通过将基类切换到 collections.Sequence(性能可能不同?!),请参阅 here)。
s = "test"
c = CharList(s)
c[1] = "oa"
# with custom __setitem__ a ValueError is raised here!
# without custom __setitem__, we could go on:
c += "er"
print c # prints "toaster"
# this looks right until here, but:
print c.list # prints ['t', 'oa', 's', 't', 'e', 'r']

3

FIFOStr 是一个支持模式匹配和可变字符串的 pypi 包。它可能并不完全符合要求,但是它是作为串口模式解析器的一部分而创建的(字符从左侧或右侧逐个添加 - 请参见文档)。它源自 deque。

from fifostr import FIFOStr

myString = FIFOStr("this is a test")
myString.head(4) == "this"  #true
myString[2] = 'u'
myString.head(4) == "thus"  #true

(完全公开,我是FIFOstr的作者)


2

在Python中,高效的可变字符串是数组

使用标准库中的array.array,以下是unicode字符串的PY3示例:

>>> ua = array.array('u', 'teststring12')
>>> ua[-2:] = array.array('u', '345')
>>> ua
array('u', 'teststring345')
>>> re.search('string.*', ua.tounicode()).group()
'string345'

bytearray是针对字节预定义的,更加自动化,涉及转换和兼容性。

对于某些情况,您还可以考虑使用memoryview/buffernumpy数组、mmapmultiprocessing.shared_memory


array自3.3版本起已被弃用,并将在4.0版本中移除。我非常怀疑,由于固定的项目大小,它无法正确处理代理对。 - undefined

0
你可以使用一个getter方法:
original_string = "hey all"
def get_string():
    return original_string

所以当你需要它的时候,就像这样给它打个电话:
get_string().split()

-3

只需要这样做
string = "big"
string = list(string)
string[0] = string[0].upper()
string = "".join(string)
print(string)

'''输出'''
  > Big


1
OP在问题描述中指出,他正在寻找比''.join(list)更有效的解决方法。他还特别要求Python提供可变字符串的库(或其他方法以实现它)。- 请修改您的答案并解释为什么从您的角度来看以这种方式进行仍然是有价值的。为未来的读者提供解释而不仅仅是“这样做”。请参见贡献指南作为参考。 - Ivo Mori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接