#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2007
    Location
    Joensuu, Finland
    Posts
    438
    Rep Power
    67

    Why can’t I subclass str (truly)


    For a very specific and hidden purpose, I tried to subclass strings. That can be done. What cannot be done, however, is modifying the __init__() function so that the new subclass could take additional parameters different from those of the superclass (which is exactly what I needed).

    You can subclass lists this way:

    Code:
    >>> class Spam(list):
        def __init__(self, data=None, eggs=0):
            if data is None:
                data = []
            list.__init__(self, data)
            self.eggs = eggs
    
    
    >>> test = Spam()
    >>> test.eggs
    0
    >>> another_test = Spam(['a', 'b', 'c'], 10)
    >>> another_test.eggs
    10
    Both tests work: I can create a Spam() object, a subclass of list, either with default or explicit values.

    With string subclasses, only default value seem to work after the first parameter:

    Code:
    >>> class MoreSpam(str):
        def __init__(self, data='', eggs=0):
            str.__init__(self, data)
            self.eggs = eggs
    
    
    >>> test = MoreSpam('')
    >>> test.eggs
    0
    >>> wtf = MoreSpam('', 10)
    Traceback (most recent call last):
      File "<pyshell#35>", line 1, in <module>
        wtf = MoreSpam('', 10)
    TypeError: str() argument 2 must be str, not int
    It seems that for whatever reason, the __init__() function of the str subclasses is not re-defining the parameter list. Instead, Python looks straight at whatever is in the base class’s parameter list, namely two string parameters: the value of the string object itself and its encoding.

    Am I getting this right? Shouldn’t this rightly be called a bug, or am I doing something completely different than I think I’m doing?
    My armada: openSUSE 13.1 (home desktop, home laptop), Crunchbang Linux 11 (work laptop), Trisquel GNU/Linux 6.0.1 (mini laptop), Ubuntu 14.04 LTS (server), Android 4.2.1 (tablet), Windows 7 Ultimate (testbed)
  2. #2
  3. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,900
    Rep Power
    481

    looks like a bug to me!


    Code:
    >>> class c(tuple):
    ...  def __init__(s,a,b):
    ...   tuple.__init__(s,a)
    ...   self.b = b
    ...
    >>> c(tuple(),666)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: tuple() takes at most 1 argument (2 given)
    I guess you'll need to write some extra code to work around this. Affects my installations of python 2 and 3.
    I'll report it to bugs.python.org
    reported
    [code]Code tags[/code] are essential for python code and Makefiles!
  4. #3
  5. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,900
    Rep Power
    481
    I'm rebuffed at bugs.python.org.

    You can't call __init__ on an immutable.

    You'll need to use type or str.__new__ or object__new__ but I completely forget how to make this work.
    [code]Code tags[/code] are essential for python code and Makefiles!
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2007
    Location
    Joensuu, Finland
    Posts
    438
    Rep Power
    67
    Originally Posted by b49P23TIvg
    I'm rebuffed at bugs.python.org.

    You can't call __init__ on an immutable.

    You'll need to use type or str.__new__ or object__new__ but I completely forget how to make this work.
    Okay, looks like subclassing immutable objects isn’t so easy I guess I could revert to subclassing “object” and use a silly class variable like self.data to hold the string. Darn. The methods would like much nicer when I could be slicing just “self” instead of “self.data”.
    My armada: openSUSE 13.1 (home desktop, home laptop), Crunchbang Linux 11 (work laptop), Trisquel GNU/Linux 6.0.1 (mini laptop), Ubuntu 14.04 LTS (server), Android 4.2.1 (tablet), Windows 7 Ultimate (testbed)
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    114
    Rep Power
    3
    Originally Posted by b49P23TIvg
    You'll need to use type or str.__new__ or object__new__ but I completely forget how to make this work.
    Code:
    class MoreSpam(str):
        def __new__(cls, data='', eggs=0):
            return str.__new__(cls, data)
    
        def __init__(self, data='', eggs=0):
            self.eggs = eggs
    Works just fine:
    Code:
    >>> test = MoreSpam('')
    >>> test.eggs
    0
    >>> wtf = MoreSpam('', 10)
    >>> wtf.eggs
    10
    Edit: You could also put everything into the __new__ method:
    Code:
    class MoreSpam(str):
        def __new__(cls, data='', eggs=0):
            spam =  str.__new__(cls, data)
            spam.eggs = eggs
            return spam

    Comments on this post

    • b49P23TIvg agrees : Please post the object creation sequence. Thanks!
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2007
    Location
    Joensuu, Finland
    Posts
    438
    Rep Power
    67
    Originally Posted by Nyktos
    Edit: You could also put everything into the __new__ method:
    Code:
    class MoreSpam(str):
        def __new__(cls, data='', eggs=0):
            spam =  str.__new__(cls, data)
            spam.eggs = eggs
            return spam
    Nice! Although the necessity to “return” anything from __new__() seems strange to me but who cares if it works and doesn’t require much extra code lines.
    My armada: openSUSE 13.1 (home desktop, home laptop), Crunchbang Linux 11 (work laptop), Trisquel GNU/Linux 6.0.1 (mini laptop), Ubuntu 14.04 LTS (server), Android 4.2.1 (tablet), Windows 7 Ultimate (testbed)

IMN logo majestic logo threadwatch logo seochat tools logo