如何在Python中子类化str

52 投票
5 回答
32150 浏览
提问于 2025-04-17 00:52

我想要创建一个新的字符串类,并给它添加一些方法。我的主要目的是学习怎么做。不过我现在卡住了,不知道是应该在一个元类里去创建这个字符串类,还是直接从 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 个回答

30

我对其他答案的复杂程度感到有点害怕,Python的标准库也是如此。你可以使用 collections.UserString 来创建一个字符串的子类,这样就不用去处理 str 的方法了。

只需要创建一个子类,然后添加你自己的方法。self.data 里面存放的就是你对象所表示的实际字符串,所以你甚至可以通过内部重新赋值 self.data 来实现字符串的“变更”方法。

这里有个例子

42

我正在尝试创建一个字符串的子类,并为它添加几个方法。我的主要目的是学习如何做到这一点。

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'>

想了解更多,可以查看这个 源代码

51

如果你想在创建字符串的时候修改它,可以重写 __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

撰写回答