How to subclass str in Python

牧云@^-^@ 提交于 2019-11-30 03:01:13
sth

Overwriting __new__() works if you want to modify the string on construction:

class caps(str):
   def __new__(cls, content):
      return str.__new__(cls, content.upper())

But if you just want to add new methods, you don't even have to touch the constructor:

class text(str):
   def duplicate(self):
      return text(self + self)

Note that the inherited methods, like for example upper() will still return a normal str, not text.

I am trying to subclass str object, and add couple of methods to it. My main purpose is to learn how to do it.

Don't hardcode the method to the parent class (like the top answer does). Instead, use super like this to support Python 2:

class Caps(str):
    def __new__(cls, content):
        return super(Caps, cls).__new__(cls, content.upper())

In Python 3, it is more performant to call super like this, but it is not backwards compatible with Python 2:

class Caps(str):
    def __new__(cls, content):
        return super().__new__(cls, content.upper())

Usage:

>>> Caps('foo')
'FOO'
>>> isinstance(Caps('foo'), Caps)
True
>>> isinstance(Caps('foo'), str)
True

The complete answer

None of the answers so far does what you've requested here:

My class's methods, should be completely chainable with str methods, and should always return a new my class instance when custom methods modified it. I want to be able to do something like this:

a = mystr("something")
b = a.lower().mycustommethod().myothercustommethod().capitalize()
issubclass(b,mystr) # True

(I believe you mean isinstance(), not issubclass().)

You need a way to intercept the string methods. __getattribute__ does this.

class Caps(str):
    def __new__(cls, content):
        return super(Caps, cls).__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 + '!!')

and now:

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

And the case requested works:

>>> a.lower().mycustommethod().myothercustommethod().capitalize()
Caps('SOMETHING!!!')

I'm kinda horrified by the complexity of the other answers, and so is python standard library. You can use collections.UserString to subclass string and do not mess with proxying str's methods.

Just subclass it, and add your methods. self.data contains the actual string that is being represented by your object, so you can even implement str-"mutating" methods by reassigning self.data internally.

An example.

Here's a quick hack to do what you want: you basically intercept every function call, and, if you see that it's returning a string, you convert it back to your own class type.

While this works in this simple example, it has some limitations. Among other things, operators such as the subscript operator are apparently not handled.

class FunWrapper(object):
    def __init__(self, attr):
        self.attr = attr

    def __call__(self, *params, **args):
        ret = self.attr(*params, **args)
        if type(ret) is str:
            return Foo(ret)
        return ret

class Foo(object):
    def __init__(self, string):
        self.string = string

    def __getattr__(self, attr):
        return FunWrapper(getattr(self.string, attr))

    def newMethod(self):
        return "*%s*" % self.string.upper()


f = Foo('hello')
print f.upper().newMethod().lower()

You can try something like:

class mystr(str):
    def new_method(self):
        pass

but you won't be sure that standard methods will return a 'mystr' instance too

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!