22

While trying to figure out how BeautifulSoup works, I incidentally learnt the __str__ method (I'm new to python). So if I did not misperceived then the __str__ method helps to shape how the class will be represented if printed out. For instance:

class Foo:
    def __str__(self):
        return "bar"

>>> x = Foo()
>>> print x
bar

Right? So asserting that I'm right, is it possible to override the __str__ method of a list of dictionaries? I mean say that in class Foo you have:

class Foo:
   def __init__(self):
      self.l = [{"Susan": ("Boyle", 50, "alive")}, {"Albert": ("Speer", 106, "dead")}]

Now is it possible to have the following outcome?

>>> x = Foo()
>>> print x.l
"Susan Boyle is 50 and alive. Albert Speer is 106 and dead."

EDIT

Considering agf's solution, how can I access the dictionary once again? I mean if I define __str__ method then apparently I should define something else to retrieve the dictionary as it is. Please consider the following example:

class PClass(dict):
    def __str__(self):
        # code to return the result that I want 

class Foo:
    def __init__(self):
        self.l = PClass({"Susan": ["Boyle", ........ })

>>> x = Foo()
>>> print x.l 
# result that works great
>>> y = x.l["Susan"] # this would not work. How can I achieve it? 
Shaokan
  • 7,438
  • 15
  • 56
  • 80

3 Answers3

22

You need to subclass the item you're pretty-printing.

from itertools import chain

class PrintableList(list): # for a list of dicts
    def __str__(self):
        return '. '.join(' '.join(str(x) for x in
            chain.from_iterable(zip((item[0], 'is', 'and'), item[1])))
                for item in (item.items()[0] for item in self)) + '.'

class PrintableDict(dict): # for a dict
    def __str__(self):
        return '. '.join(' '.join(str(x) for x in
            chain.from_iterable(zip((item[0], 'is', 'and'), item[1])))
                for item in self.iteritems()) + '.'

class Foo:
   def __init__(self):
      self.d = PrintableDict({"Susan": ("Boyle", 50, "alive"), 
                              "Albert": ("Speer", 106, "dead")})

class Bar:
   def __init__(self):
      self.l = PrintableList([{"Susan": ("Boyle", 50, "alive")}, 
                              {"Albert": ("Speer", 106, "dead")}])

foo = Foo()
print self.d
bar = Bar()
print self.l
agf
  • 171,228
  • 44
  • 289
  • 238
  • congrats on 10k by the way :) – Shaokan Aug 22 '11 at 19:26
  • Thanks. I lost my internet connection for a few minutes but now I fixed it so the `__str__` actually works for the example (not that that matters) – agf Aug 22 '11 at 19:29
  • You need to look back at your question again :). You had a _list of dictionaries_, so you can't access them by name. If you want to access them by name, you need to make the whole thing a dictionary. It has nothing to do with the string method. – agf Aug 23 '11 at 12:00
  • I updated my answer with a version for a dict. It's almost exactly the same. – agf Aug 23 '11 at 12:09
4

Another alternative is to override __getattribute__, which lets you customize how attributes are returned:

class Foo(object):
    def __init__(self):
        self.l = [{"Susan": ("Boyle", 50, "alive")}, {"Albert": ("Speer", 106, "dead")}]

    def __getattribute__(self, name):
        return PrintableList(l)
        attr = super(Foo, self).__getattribute__(name)
        items = sum([x.items() for x in attr], [])
        return ' '.join([' '.join([k, v[0], 'is', str(v[1]), 'and', v[2] + '.']) for k,v in items])

>>> f = Foo()
>>> print f.l
<<< Susan Boyle is 50 and alive. Albert Speer is 106 and dead.
Zach Kelling
  • 52,505
  • 13
  • 109
  • 108
0

You could define __str__ for your Foo class to return what you want:

class Foo():
    def __init__(self):
        self.l = [{"Susan": ("Boyle", 50, "alive")}, {"Albert": ("Speer", 106, "
dead")}]
    def __str__(self):
        ret_str = ""
        for d in self.l:
            for k in d:
                ret_str += "".join([k, " ", d[k][0], " is ", str(d[k][1]), " and
 ", d[k][2], ". "])
        return ret_str

foo = Foo()
print foo

results in:

Susan Boyle is 50 and alive. Albert Speer is 106 and dead.

GreenMatt
  • 18,244
  • 7
  • 53
  • 79