【发布时间】:2016-01-16 21:03:08
【问题描述】:
我正在尝试继承 str - 不是为了任何重要的事情,只是为了了解更多关于 Python 内置类型的实验。我以这种方式对str 进行了子类化(使用__new__,因为str 是不可变的):
class MyString(str):
def __new__(cls, value=''):
return str.__new__(cls, value)
def __radd__(self, value): # what method should I use??
return MyString(self + value) # what goes here??
def write(self, data):
self.__radd__(data)
据我所知,它初始化正确。但我不能让它使用 += 运算符就地修改自己。我尝试过覆盖__add__、__radd__、__iadd__ 和各种其他配置。使用return 语句,我设法让它返回正确附加MyString 的新实例,但没有就地修改。成功应该是这样的:
b = MyString('g')
b.write('h') # b should now be 'gh'
有什么想法吗?
更新
为了可能添加某人可能想要这样做的原因,我遵循了创建以下在内部使用纯字符串的可变类的建议:
class StringInside(object):
def __init__(self, data=''):
self.data = data
def write(self, data):
self.data += data
def read(self):
return self.data
并用 timeit 测试:
timeit.timeit("arr+='1234567890'", setup="arr = ''", number=10000)
0.004415035247802734
timeit.timeit("arr.write('1234567890')", setup="from hard import StringInside; arr = StringInside()", number=10000)
0.0331270694732666
number 的差异迅速增加 - 在 100 万次互动时,StringInside 花费的时间比我愿意等待返回的时间要长,而纯 str 版本在大约 100 毫秒内返回。
更新 2
为了后代,我决定编写一个包装 C++ 字符串的 cython 类,以查看与基于以下 Mike Müller 更新版本的松散类相比性能是否可以提高,并且我成功了。我意识到 cython 是“作弊”,但我提供这个只是为了好玩。
python 版本:
class Mike(object):
def __init__(self, data=''):
self._data = []
self._data.extend(data)
def write(self, data):
self._data.extend(data)
def read(self, stop=None):
return ''.join(self._data[0:stop])
def pop(self, stop=None):
if not stop:
stop = len(self._data)
try:
return ''.join(self._data[0:stop])
finally:
self._data = self._data[stop:]
def __getitem__(self, key):
return ''.join(self._data[key])
cython 版本:
from libcpp.string cimport string
cdef class CyString:
cdef string buff
cdef public int length
def __cinit__(self, string data=''):
self.length = len(data)
self.buff = data
def write(self, string new_data):
self.length += len(new_data)
self.buff += new_data
def read(self, int length=0):
if not length:
length = self.length
return self.buff.substr(0, length)
def pop(self, int length=0):
if not length:
length = self.length
ans = self.buff.substr(0, length)
self.buff.erase(0, length)
return ans
性能:
写作
>>> timeit.timeit("arr.write('1234567890')", setup="from pyversion import Mike; arr = Mike()", number=1000000)
0.5992741584777832
>>> timeit.timeit("arr.write('1234567890')", setup="from cyversion import CyBuff; arr = CyBuff()", number=1000000)
0.17381906509399414
阅读
>>> timeit.timeit("arr.write('1234567890'); arr.read(5)", setup="from pyversion import Mike; arr = Mike()", number=1000000)
1.1499049663543701
>>> timeit.timeit("arr.write('1234567890'); arr.read(5)", setup="from cyversion import CyBuff; arr = CyBuff()", number=1000000)
0.2894480228424072
弹出
>>> # note I'm using 10e3 iterations - the python version wouldn't return otherwise
>>> timeit.timeit("arr.write('1234567890'); arr.pop(5)", setup="from pyversion import Mike; arr = Mike()", number=10000)
0.7390561103820801
>>> timeit.timeit("arr.write('1234567890'); arr.pop(5)", setup="from cyversion import CyBuff; arr = CyBuff()", number=10000)
0.01501607894897461
【问题讨论】:
-
您不能就地修改字符串,因为它们是不可变的。唯一的选择是创建一个新字符串。
-
我很确定,通过继承一个不可变的类,新类根据定义也是不可变的,因此您无法就地更改它。要获得您想要的行为,您必须创建一个可变类,该类可能具有
str作为您更改的属性。 -
是的,我知道,但如果
i = 'ab',那么i += 'c',我将是'abc'。在引擎盖下,它是一个新字符串,当然,这是我试图复制的行为 - 但使用write方法来做到这一点。 -
这没有意义,因为字符串在
__radd__中不会这样做。它只是退回到i = i + 'c'。 -
另一种表述问题的方式是“我如何使
MyString上的write方法与str上的+= 方法相同:如果我持有值'ab' ,然后在i.write('c')之后,我持有'abc'。
标签: python string subclass immutability built-in