inheritance from str or int

29,853

Solution 1

>>> class C(str):
...     def __new__(cls, *args, **kw):
...         return str.__new__(cls, *args, **kw)
... 
>>> c = C("hello world")
>>> type(c)
<class '__main__.C'>

>>> c.__class__.__mro__
(<class '__main__.C'>, <type 'str'>, <type 'basestring'>, <type 'object'>)

Since __init__ is called after the object is constructed, it is too late to modify the value for immutable types. Note that __new__ is a classmethod, so I have called the first parameter cls

See here for more information

>>> class C(str):
...     def __new__(cls, value, meta):
...         obj = str.__new__(cls, value)
...         obj.meta = meta
...         return obj
... 
>>> c = C("hello world", "meta")
>>> c
'hello world'
>>> c.meta
'meta'

Solution 2

Inheriting built-in types is very seldom worth while. You have to deal with several issues and you don't really get much benefit.

It is almost always better to use composition. Instead of inheriting str, you would keep a str object as an attribute.

class EnhancedString(object):
     def __init__(self, *args, **kwargs):
         self.s = str(*args, **kwargs)

you can defer any methods you want to work on the underlying str self.s manually or automatically using __getattr__.

That being said, needing your own string type is something that should give you pause. There are many classes that should store a string as their main data, but you generally want to use str or unicode (the latter if you're representing text) for general representation of strings. (One common exception is if you have need to use a UI toolkit's string type.) If you want to add functionality to your strings, try if you can to use functions that operate on strings rather than new objects to serve as strings, which keeps your code simpler and more compatible with everyone else's programs.

Solution 3

When you instantiate a class, the arguments that you pass in, are passed to both the __new__ (constructor) and then to the __init__ (initializer) methods of the class. So if you inherit from a class that has restrictions on number of arguments that may be supplied during instantiation, you must guarantee that neither its __new__, nor its __init__ would get more arguments than they expect to get. So that is the problem that you have. You instantiate your class with C("a", "B"). The interpreter looks for __new__ method in C. C doesn't have it, so python peeps into its base class str. And as it has one, that one is used and supplied with the both arguments. But str.__new__ expects to get only one argument (besides its class object as the first argument). So TypeError is raised. That is why you must extend it in your child class similarly to what you do with __init__. But bear in mind that it must return class instance and that it is a static method (irrespective of whether it is defined with @staticmethod decorator or not) that counts if you use super function.

Solution 4

Use __new__ in case of immutable types:

class C(str):
    def __new__(cls, content, b):
        return str.__new__(cls, content)

    def __str__(self):
        return str.__str__(self)

a=C("hello", "world")
print a

print returns hello.

Python strings are immutable types. The function __new__ is called to create a new instance of object C. The python __new__ function is basically exists to allow inheritance from immutable types.

Solution 5

After carefully reading this, here is another attempt at subclassing str. The change from other answers is creating the instance in the correct class using super(TitleText, cls).__new__ . This one seems to behave like a str whenever it's used, but has allowed me to override a method:

class TitleText(str):
    title_text=""
    def __new__(cls,content,title_text):
        o=super(TitleText, cls).__new__(cls,content)
        o.title_text = title_text
        return o

    def title(self):
        return self.title_text

>>> a=TitleText('name','A nice name')
>>> a
'name'
>>> a[0]
'n'
>>> a[0:2]
'na'
>>> a.title()
'A nice name'

This lets you do slicing and subscripting correctly. What's this for? For renaming the Django application in the admin index page.

Share:
29,853
Ruggero Turra
Author by

Ruggero Turra

Updated on April 24, 2021

Comments

  • Ruggero Turra
    Ruggero Turra about 3 years

    Why I have problem creating a class inheriting from str (or also from int)

    class C(str):
       def __init__(self, a, b):
         str.__init__(self,a)
         self.b = b
    
    C("a", "B")
    
    TypeError: str() takes at most 1 argument (2 given)
    

    the same happens if I try to use int instead of str, but it works with custom classes. I need to use __new__ instead of __init__? why?

  • Ruggero Turra
    Ruggero Turra about 14 years
    type(C("a", "B")) -> NoneType
  • Ruggero Turra
    Ruggero Turra about 14 years
    do you try your answer? Now it return ''
  • zoli2k
    zoli2k about 14 years
    And have you checked the updates of the post? It was updated according your request. You down-voted after the changes were made. Moreover, in your comment you expected functionality from my code which was not requested in your question (to get __str__() function work for the inherited class)!
  • Ruggero Turra
    Ruggero Turra about 14 years
    After the first implementation the code returned NoneType, after the second it gave "". Which not requested functionality? I didn't ask a __str__ function and it is not necessary, because the class inherited from str, see gnibbler answer. Now I can't modify the downvote, I need that you edit your answer.
  • Ruggero Turra
    Ruggero Turra about 14 years
    yes, this was my first solution, but the problem is that I have some functions that want a string argument (they are C++ functions from a library I can't modify), so with my implementation these functions work using my class as arguments. Can you better explain how to obtain this result using __getattr__?
  • Mike Graham
    Mike Graham about 14 years
    @wiso, accepting a string as an argument is an underdefined thing. I don't know what your C++ library is like and how it's wrapped, but a wrapper should make it compatible with the right types for the job. Making a new class yourself that no one has ever seen before rather than using its string class or Python's string class (and it working with that) seems like a strange way to make things work.
  • Mike Graham
    Mike Graham about 14 years
    @wise, To use __getattr__ to defer otherwise undefined attributes to a specific attribute in composition is accomplished by code like def __getattr__(self, name): return getattr(self.s, name). Note that this code isn't necessarily recommended, but can work well in some cases; it's often better to manually define the things you want in composition.
  • Ruggero Turra
    Ruggero Turra about 14 years
    I have c_function(char*), I can use it in python as c_function(s) where s is a str instance. s represent something, and I want that it has a name, a beautiful_name and so on. So I have derived a class C from str, so I can pass an object c of class C to python_function that for example do print c.name and call c_function(c)
  • blais
    blais about 10 years
    > "Inheriting built-in types is very seldom worth while." That might be true, but not always. Sometimes it's useful to create a "tagged string" type, such as to produce distinct types of string-like tokens from a parser. The problem with it, however, is that (I think) you lose the short-string performance optimization.
  • Mike Graham
    Mike Graham about 10 years
    @blais, in that case, composition is still much less error-prone. Inheriting from str is a potential source of error, but doesn't actually help you.
  • Mike Graham
    Mike Graham about 10 years
    Yuck! The fact that your repr is deceptive leads to it being unclear that while a is a TitleText, a[0] and a[0:2] are str (today, in current versions, as an implementation detail). If you'd written a class without inheriting str you could make a much more useful and much less confusing and deceptive thing.
  • blais
    blais about 10 years
    I'm not convinced. Can you point out why you think inheriting from str is a potential source of error? Because it's not idiomatic?
  • blais
    blais about 10 years
    docs.python.org/3/library/… "The class, UserString acts as a wrapper around string objects. The need for this class has been partially supplanted by the ability to subclass directly from str;" It's not that unidiomatic.
  • Mike Graham
    Mike Graham about 10 years
    @blais, Suppose you do your_subclass[:5] or your_subclass.replace(x, y)--what does it return? An instance of str or an instance of your subclass? This isn't even defined, but in practice it will return str. There is no guarantee any which way, and this sort of undefined behavior is a bad thing.
  • Mike Graham
    Mike Graham about 10 years
    @blais, you should always write code that is as plain as possible and as easy to understand what it does as possible. Subclassing builtins is simply hard to think about, maintain, and use.
  • Evgeni Sergeev
    Evgeni Sergeev almost 9 years
    To verify, try this at the prompt: >>> str.__init__('abc', 'def'). This does nothing. However, >>> str.__new__(str, 'abc', 'def') throws the exception: TypeError: str() takes at most 1 argument (2 given). This is a bit confusing, because we called str.__new__(..) with 3 arguments, not 2. Does this mean that str.__new__(..) accepts any number of arguments, but then calls str(..)? Probably not, because str(..) is going to call str.__new__(..) in turn...
  • user2683246
    user2683246 almost 9 years
    @Evgeni: str class object doesn't implement __init__ method at all. Nor does basestring (str's base class). __init__ is inherited directly from object and that one accepts any number of arguments. On the contrary __new__ method is really implemented in str and it accepts at most 1 argument beside the first one that must be str or its subclass. Try expressions like >>> '__init__' in str.__dict__ and so on for __new__, basestring and object.
  • Evgeni Sergeev
    Evgeni Sergeev almost 9 years
    That's a good way to check which methods are implemented. I'm still a bit confused why I write str.__new__(str, 'abc', 'def') and it says I supplied 2 arguments. I count three there. For the example class C(object):\n @staticmethod \n def m(a): pass, if I call it like C.m(1, 2), it will say TypeError: m() takes exactly 1 argument (2 given) as expected. I guess __new__ produces a confusing message because __new__ is special (e.g. you don't need to add the @staticmethod when implementing it).
  • JamesThomasMoon
    JamesThomasMoon over 5 years
    I used your answer to help answer my own SO question here. Thanks!