user102975 user102975 - 4 months ago 12
Python Question

Extending Python's builtin Str

I'm trying to subclass

str
, but having some difficulties due to its immutability.

class DerivedClass(str):

def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob

def upper(self):
#overridden, new functionality. Return ob of type DerivedClass. Great.
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')

derived = DerivedClass('a')

print derived.upper() #'A123'
print type(derived.upper()) #<class '__main__.DerivedClass'>
print derived.lower() #'a'
print type(derived.lower()) #<type 'str'>


For inherited methods that don't require any new functionality, such as
derived.lower()
, is there a simple, pythonic way to return an object of type
DerivedClass
(instead of
str
)? Or am I stuck manually overriding each str.method(), as I did with
derived.upper()
?

Edit:

#Any massive flaws in the following?

class DerivedClass(str):
def __new__(cls, string):
ob = super(DerivedClass, cls).__new__(cls, string)
return ob

def upper(self):
caps = super(DerivedClass, self).upper()
return DerivedClass(caps + '123')

def __getattribute__(self, name):
att = super(DerivedClass, self).__getattribute__(name)

if not callable(att):
return att

def call_me_later(*args, **kwargs):
result = att(*args, **kwargs)
if isinstance(result, basestring):
return DerivedClass(result)
return result
return call_me_later

Answer

You can do this by overriding __getattribute__ as Zr40 suggests, but you will need to have getattribute return a callable function. The sample below should give you what you want; it uses the functools.partial wrapper to make life easier, though you could implement it without partial if you like:

from functools import partial

class DerivedClass(str):

    def __new__(cls, string):
        ob = super(DerivedClass, cls).__new__(cls, string)
        return ob

    def upper(self):
        #overridden, new functionality. Return ob of type DerivedClass. Great.
        caps = super(DerivedClass, self).upper()
        return DerivedClass(caps + '123')

    def __getattribute__(self, name):
        func = str.__getattribute__(self, name)
        if name == 'upper':
            return func

        if not callable(func):
            return func

        def call_me_later(*args, **kwargs):
            result = func(*args, **kwargs)
            # Some str functions return lists, ints, etc
            if isinstance(result, basestring:
                return DerivedClass(result)
            return result

        return partial(call_me_later)
Comments