How to subclass str in Python
Solution 1
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
.
Solution 2
I am trying to subclass str object, and add couple of methods to it. My main purpose is to learn how to do it.
UserString
was created before it was possible to subclass str
directly, so prefer to subclass str
, instead of using UserString
(as another answer suggests).
When subclassing immutable objects, it's usually necessary to modify the data before you instantiate the object - therefore you need to both implement __new__
and call the parent __new__
(preferably with super
, instead of str.__new__
as another answer suggests).
In Python 3, it is more performant to call super
like this:
class Caps(str):
def __new__(cls, content):
return super().__new__(cls, content.upper())
__new__
looks like a class method, but it is actually implemented as a static method, so we need to pass cls
redundantly as the first argument. We don't need the @staticmethod
decorator, however.
If we use super
like this to support Python 2, we'll note the redundant cls
more clearly:
class Caps(str):
def __new__(cls, content):
return super(Caps, cls).__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().__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!!!')
Response to Comment
Why is the Python 3 only call, i.e. super().method(arg) more performant?
The function already has access to both __class__
and self
without doing a global and local lookup:
class Demo:
def foo(self):
print(locals())
print(__class__)
>>> Demo().foo()
{'self': <__main__.Demo object at 0x7fbcb0485d90>, '__class__': <class '__main__.Demo'>}
<class '__main__.Demo'>
See the source for more insight.
Solution 3
I'm kinda horrified by the complexity of the other answers, and so is Python's 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.
Solution 4
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()
Comments
-
yasar almost 2 years
I am trying to subclass str object, and add couple of methods to it. My main purpose is to learn how to do it. Where I am stuck is, am I supposed to subclass string in a metaclass, and create my class with that meta, or subclass str directly?
And also, I guess I need to implement
__new__()
somehow, because, my custom methods will modify my string object, and will return new mystr obj.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 want to have it all the abilities that a
str
have. For example,a = mystr("something")
then I want to use it like, a.capitalize().mycustommethod().lower()It is my understanding that, I need to implement
__new__()
. I think so because, strings methods would probably try to create new str instances. So , if I overwrite__new__()
, They supposedly would return my custom str class. However, I don't know how to pass arguments to my custom class's__init__()
method in that case. And I guess I would need to usetype()
in order to create a new instance in__new__()
method right? -
Robert Lujo over 8 yearsMore generic approach based on __init__(self, *args, **kwargs) + super() is reporting DeprecationWarning: object.__init__() takes no parameters. Thus, I assume __new__(cls, *args, **kwargs) is a better way to do it. Or? Maybe this explains better: jfine-python-classes.readthedocs.org/en/latest/…
-
dreftymac almost 6 yearsSee also:
six.moves UserString
stackoverflow.com/search?q="six.moves" pythonhosted.org/six/#module-six.moves -
Christopher Barber over 4 yearsBut UserString is not a subclass for str so that is not an option if you need isinstance checks to work.
-
Hugo Mota over 4 yearsNice answer +1. Not json serializable tho.
-
Jiří over 3 yearsalso, overriding
__str__()
is worth mentioning, because accessingself
as a value can quickly lead to infinite loop. For example, to always enclose the string, usef'"{super().__str__()}"'
instead ofstr(super())
which will return the superclass description instead of the value -
Leif Arne Storset over 3 yearsSubscripting can be handled using
__getitem__
, and there are magic methods for the other operators. -
rv.kvetch over 2 yearsHi, quick question but from testing I noticed that
__getattribute__
does not handle methods like__add__
, for ex.Caps('a') + 'b'
. Was wondering if anyone know how to automatically handle such arithmetic methods, such that it returns aCaps
instance. -
Russia Must Remove Putin over 2 years@rv.kvetch - looks like you have a couple of options - your question is answered here, note that I don't vouch for the answers though stackoverflow.com/questions/9057669/…