如何在Python中子类化str
我想要创建一个新的字符串类,并给它添加一些方法。我的主要目的是学习怎么做。不过我现在卡住了,不知道是应该在一个元类里去创建这个字符串类,还是直接从 str 这个类继承。
另外,我觉得我需要实现 __new__()
这个方法,因为我的自定义方法会修改字符串对象,并返回一个新的 mystr 对象。
我的类的方法应该能和 str 的方法完全连贯使用,而且每当自定义方法修改了对象时,都应该返回一个新的我的类的实例。我想要能够像这样使用:
a = mystr("something")
b = a.lower().mycustommethod().myothercustommethod().capitalize()
issubclass(b,mystr) # True
我希望它具备所有 str
的功能。例如,a = mystr("something")
,然后我想用它像这样:
a.capitalize().mycustommethod().lower()
我理解的是,我需要实现 __new__()
。我这么认为是因为,字符串的方法可能会尝试创建新的 str 实例。所以,如果我重写了 __new__()
,它们应该会返回我的自定义字符串类。不过,我不知道在这种情况下,怎么把参数传给我自定义类的 __init__()
方法。我想我可能需要在 __new__()
方法里使用 type()
来创建一个新的实例,对吧?
5 个回答
我对其他答案的复杂程度感到有点害怕,Python的标准库也是如此。你可以使用 collections.UserString 来创建一个字符串的子类,这样就不用去处理 str
的方法了。
只需要创建一个子类,然后添加你自己的方法。self.data
里面存放的就是你对象所表示的实际字符串,所以你甚至可以通过内部重新赋值 self.data
来实现字符串的“变更”方法。
我正在尝试创建一个字符串的子类,并为它添加几个方法。我的主要目的是学习如何做到这一点。
UserString
是在可以直接创建 str
子类之前就存在的,所以建议直接创建 str
的子类,而不是使用 UserString
(就像其他回答所建议的那样)。
在创建不可变对象的子类时,通常需要在实例化对象之前修改数据——因此你需要实现 __new__
方法,并调用父类的 __new__
(最好用 super
,而不是像其他回答那样用 str.__new__
)。
在 Python 3 中,像这样调用 super
的性能更好:
class Caps(str):
def __new__(cls, content):
return super().__new__(cls, content.upper())
__new__
看起来像一个类方法,但实际上它是作为静态方法实现的,所以我们需要多余地将 cls
作为第一个参数传入。不过,我们不需要 @staticmethod
装饰器。
如果我们像这样使用 super
来支持 Python 2,我们会更清楚地注意到多余的 cls
:
class Caps(str):
def __new__(cls, content):
return super(Caps, cls).__new__(cls, content.upper())
用法:
>>> Caps('foo')
'FOO'
>>> isinstance(Caps('foo'), Caps)
True
>>> isinstance(Caps('foo'), str)
True
完整的答案
到目前为止,没有任何答案满足你在这里的要求:
我的类的方法应该可以和字符串的方法完全链式调用,并且在自定义方法修改了它时,应该始终返回一个新的我的类实例。我想能够做到这样的事情:
a = mystr("something") b = a.lower().mycustommethod().myothercustommethod().capitalize() issubclass(b,mystr) # True
(我相信你是指 isinstance()
,而不是 issubclass()
。)
你需要一种方法来拦截字符串的方法。__getattribute__
就可以做到这一点。
class Caps(str):
def __new__(cls, content):
return super().__new__(cls, content.upper())
def __repr__(self):
"""A repr is useful for debugging"""
return f'{type(self).__name__}({super().__repr__()})'
def __getattribute__(self, name):
if name in dir(str): # only handle str methods here
def method(self, *args, **kwargs):
value = getattr(super(), name)(*args, **kwargs)
# not every string method returns a str:
if isinstance(value, str):
return type(self)(value)
elif isinstance(value, list):
return [type(self)(i) for i in value]
elif isinstance(value, tuple):
return tuple(type(self)(i) for i in value)
else: # dict, bool, or int
return value
return method.__get__(self) # bound method
else: # delegate to parent
return super().__getattribute__(name)
def mycustommethod(self): # shout
return type(self)(self + '!')
def myothercustommethod(self): # shout harder
return type(self)(self + '!!')
现在:
>>> a = Caps("something")
>>> a.lower()
Caps('SOMETHING')
>>> a.casefold()
Caps('SOMETHING')
>>> a.swapcase()
Caps('SOMETHING')
>>> a.index('T')
4
>>> a.strip().split('E')
[Caps('SOM'), Caps('THING')]
而且请求的情况可以正常工作:
>>> a.lower().mycustommethod().myothercustommethod().capitalize()
Caps('SOMETHING!!!')
对评论的回应
为什么 Python 3 中的调用,比如 super().method(arg) 性能更好?
这个函数已经可以直接访问 __class__
和 self
,而不需要进行全局和局部查找:
class Demo:
def foo(self):
print(locals())
print(__class__)
>>> Demo().foo()
{'self': <__main__.Demo object at 0x7fbcb0485d90>, '__class__': <class '__main__.Demo'>}
<class '__main__.Demo'>
想了解更多,可以查看这个 源代码。
如果你想在创建字符串的时候修改它,可以重写 __new__()
这个方法:
class caps(str):
def __new__(cls, content):
return str.__new__(cls, content.upper())
但是如果你只是想添加一些新方法,其实不需要动构造函数:
class text(str):
def duplicate(self):
return text(self + self)
需要注意的是,像 upper()
这样的继承方法仍然会返回一个普通的 str
,而不是 text
。