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.
a.capitalize()
will return a standard, unmodifiedstr
, not your custom class, soa.capitalize().mycustommethod()
will fail. It is far, far better coding practice to just write a couple functions and just domycustommethod(a.capitalize()).lower()
, because this will not confuse everyone else who reads your code (BTW, "everyone else" includes "you, two years from now"). – Langsyne