tags:

views:

764

answers:

7

This seems like it should be simple:

I want a list like any other list, except it has a different .__str__ method.

  1. Trying to set object.__str__ = foo results in a read-only error
  2. Trying to subclass list means you need some way to convert an existing list to an instance of the subclass. This requires either copying all attributes manually (a huge pain), or somehow copying them all automatically, which I don't know how to do.
  3. Trying to write a wrapper around the list object means I have to figure out some way to send all messages to the wrapped object except .__str__ which I handle with my own method. Don't know how to do this.

Any alternatives, or solutions #2 or #3 greatly appreciated. Thanks!

+4  A: 

You could extend the list class and override it:

class myList(list):
  def __str__(self):
    # do something
    return "something"

Edit: removed an incorrect part of the answer which suggested dynamically replacing __str__ on the list object, which is not allowed in the implementation of Python lists.

saffsd
You can't modify list's __str__: >>> list([1, 2, 3]).__str__ = myString Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object attribute '__str__' is read-only . But it can be done fine with inheritance.
Matthew Flaschen
This seems close, but how do you convert a `list` to a `myList`?E.g. I got a `list` out of a list comprehension. Now what do I do with it?
elliot42
See my post above.
Matthew Flaschen
This answer is cryptic. What's the "Edit: my bad" all about? Can you fix this to be a stand-alone answer that doesn't require reading comments from the question or other answers? There's not "above" in SO, since the answer order changes with the votes.
S.Lott
Actually, it's a comment so it's still above. But you're the second person to ask for a stand-alone answer, so...
Matthew Flaschen
+2  A: 

I'm a Java programmer but I think that is what you want (tested with python 2.6):

>>> class myList(list):
...   def __str__(self):
...     return "aaa"
...
>>> def myListWrapper(list):
...   return myList(list)
...
>>> a = [1, 2, 3]
>>> type(a)
<type 'list'>
>>> b = myListWrapper(a)
>>> type(b)
<class '__main__.myList'>
>>> print(a)
[1, 2, 3]
>>> print(b)
aaa
dfa
Very nice, thanks. I didn't understand that myList(list) would automatically copy all the data from the list instance into the myList instance. I presume this is only because the implementers of list explicitly created a copy-constructor-like function, and not because this is generic to all inheritance.
elliot42
The further questions, would be, of course, how much work is it to write that function that does all the copying. Did they have to copy every attribute by hand?
elliot42
list class has this constructor "list(sequence) -> new list initialized from sequence's items"
dfa
What's the point of myListWrapper?
Matthew Flaschen
myListWrapper takes a list object and return a myList
dfa
That is a dangerous solution. If run any list operation that return a new list on myList the result will be a native python list. For example myList() + myList()
Nadia Alramli
good point. You're right but I see myList only as container for a replacement of __str__. IMHO it is not a fully-featured list.
dfa
Yes, dfa, but myListWrapper(whatever) does the exact same thing as myList(whatever)
Matthew Flaschen
true :-) but I preferred to make this explicit
dfa
A: 

How about wrapping the list?

>>> class StrList(object):
    def __init__(self, data=None):
     self._data = data or []
    def __str__(self):
     return "StrList!"
    def __getattr__(self, attr):
     if attr == "_data":
      return self.__dict__[attr]
     return getattr(self._data, attr)
    def __setattr__(self, key, val):
     if key == "_data":
      self.__dict__[key] = val
     else:
      setattr(self._data, key, val)
    def __getitem__(self, index):
     return self._data[index]
    def __setitem__(self, index, value):
     self._data[index] = value


>>> l = StrList(range(3))
>>> print l
StrList!
>>> l[0]
0
>>> for x in l:
    print x


0
1
2
>>> l[0] = "spam"
>>> l.append("egg")
>>> print list(l)
['spam', 1, 2, 'egg']
>>>
Ryan Ginstrom
This is interesting, but honestly I don't understand why or how l.append in the example works. What tells it how to pass the call to append from the StrList to the list within it?
elliot42
__getattr__ is called when you call an object with a method/member it doesn't have. We can then pass that call down to our wrapped list. The same applies to __setattr__ et al. You have to override the call to "_data", however, to avoid infinite recursion.
Ryan Ginstrom
+1  A: 

Which raises the question: why do you want to override the __str__ method?

Wouldnt it be better to create a class to encapsulate your object?

class MyContainer(object):
    def __init__(self, list):
        self.containedList = list

    def __str__(self):
        print('All hail Python')

This way you don't have to worry about converting your object, or copying the attributes, or whatsoever. (by the way, how expensive is MyList(longlist)? Is it an intelligent copy, or a dumb "let's recreate a list object from an iterable?")

If, at some point, it looks complicated to do what you're trying to do, it might mean that you're doing it wrong :p

NicDumZ
If you encapsulate rather than subclass you don't get all the list functionality for free, unless you a) rewrite all the list methods on the MyContainer, or b) you have to always be calling *into* container_instance.containedList. This would be a superior solution if I had a complex object that also happened to need a list inside it (e.g. class needs a list, a string, a few ints, etc..). But as it is all I need is the list just slightly slightly modified.
elliot42
+3  A: 

This solution works without a wrapper. And works if you join two lists by add. Any operation that modify the list itself will work as expected. Only functions that return a copy of the list like: sorted, reveresed will return the native python list which is fine. sort and reverse on the other hand operate on the list itself and will keep the type.

class myList(list):
    def __new__(cls, data=None):
        obj = super(myList, cls).__new__(cls, data)
        return obj

    def __str__(self):
        return 'myList(%s)' % list(self)

    def __add__(self, other):
        return myList(list(self) + list(other))

>>> l = myList(range(5))
>>> print l
myList([0, 1, 2, 3, 4])
>>> print l + [1, 2]
myList([0, 1, 2, 3, 4, 1, 2])
>>> l.sort()
>>> print l
myList([0, 1, 2, 3, 4])
Nadia Alramli
This is fascinating and awesome. I take it for a full solution would need to override all the operator methods. I wonder what it would take to get sorted and reversed to work too (not something you need to answer though). Thanks!
elliot42
You probably want to replace the first `list(self)` by super(myList, self).__str__() and the `list(self) + list(other)` by super(myList, self).__add__(other)
NicDumZ
+3  A: 

If you would like to override __str__ for other containers (e.g., tuple), you can take advantage of multiple inheritance:

class PrettyStr(object):
    def __str__(self):
        ret = ''

        if isinstance(self, (list, tuple)):
            ret = ''.join(str(elem) for elem in self)
        else:
            pass  # handle other types here

        return ret


class MyList(PrettyStr, list):
    pass


class MyTuple(PrettyStr, tuple):
    pass


if __name__ == "__main__":
    print MyList([1, 2, 3, 4])
    print MyTuple((1, 2, 3, 4))
Bastien Léonard
Whoa, that's very interesting! Thanks!
elliot42
+2  A: 
class MyList(list):
     def __str__(self):
             return "foo"

str(MyList([1, 2, 3]))

'foo'

str(MyList(list([1, 2, 3])))

'foo'

My earlier comments as an answer. As you can see MyList accepts any sequence in its constructor.

Matthew Flaschen