6

I'm trying to create a derived class that inherits from both a str type and a second class. It's problematic since the str type doesn't simply call __init__, but the __new__ method due to its immutability. I know that for __init__ and super to work well, you need to have the same calling structure all the way down. However the following implementation fails:

class base(object):
    def __new__(cls, *args, **kwargs):
        print "NEW  BASE:", cls, args, kwargs
        return super(base, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print "INIT BASE", args, kwargs

class foo(base, str):
    def __new__(cls, *args, **kwargs):
        return super(foo, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        super(foo, self).__init__(*args, **kwargs)

Here foo('cat') works with:

>> NEW  BASE: <class '__main__.foo'> ('cat',) {}
>> INIT BASE ('cat',) {}

but with an argument foo('cat', x=3), it fails:

>> NEW  BASE: <class '__main__.foo'> ('cat',) {'x': 3}
Traceback (most recent call last):
  File "inh.py", line 19, in <module>
    foo('cat', x=3)
  File "inh.py", line 12, in __new__
    return super(foo, cls).__new__(cls, *args, **kwargs)
  File "inh.py", line 4, in __new__
    return super(base, cls).__new__(cls, *args, **kwargs)
TypeError: str() takes at most 1 argument (2 given)

I can get this to work by changing the base.__new__ method to:

def __new__(cls, *args, **kwargs):
    return super(base, cls).__new__(cls)

but now I've changed the calling structure, which I feel will cause me problems later down the line.

How do I properly inherit from a string and a second class?

Hooked
  • 84,485
  • 43
  • 192
  • 261

1 Answers1

3

You can't just do

def __new__(cls, *args, **kwargs):
    return super(base, cls).__new__(cls)

because this will cause incorrect call for new of str (you will not pass allowed argument

>>> foo('t')
NEW  BASE: <class '__main__.foo'> ('t',) {}
INIT BASE ('t',) {}
''

You should do something like

def __new__(cls, *args, **kwargs):
    return super(base, cls).__new__(cls, *args[:1])

But this can broken something if You will use base class as mixin for class which __new__ method accept more than one argument.

as a option maybe You should have class inherited from str but with overridden new method:

class CarelessStr(str):
    def __new__(cls, *args, **kwargs):
        return super(CarelessStr, cls).__new__(cls, *args[:1])

class foo(base, CarelessStr):
    def __new__(cls, *args, **kwargs):
        return super(foo, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        super(foo, self).__init__(*args, **kwargs)
oleg
  • 4,082
  • 16
  • 16
  • You're correct that my new doesn't pass the first argument. However your solution seems kind of painful, since it adds another layer of indirection. Admittedly it works, but is it considered the pythonic way? – Hooked May 30 '13 at 19:49
  • I am not sure that there is generic solution for described task. I agree that this solution adds another layer of indirection. But this can be the only option for some cases. – oleg May 30 '13 at 19:54
  • Awesome. An extra str base hadn't occurred to me! – Rafe Apr 28 '14 at 17:31