Login | Register For Free | Help
Search for: (Advanced)

Mailing List Archive: Python: Python

Dealing with the __str__ method in classes with lots of attributes

 

 

Python python RSS feed   Index | Next | Previous | View Threaded


andreas.tawn at ubisoft

May 10, 2012, 6:33 AM

Post #1 of 17 (1428 views)
Permalink
Dealing with the __str__ method in classes with lots of attributes

Say I've got a class...

class test(object):
def __init__(self):
self.foo = 1
self.bar = 2
self.baz = 3

I can say...

def __str__(self):
return "foo: {0}\nbar: {1}\nbaz: {2}".format(self.foo, self.bar, self.baz)

and everything's simple and clean and I can vary the formatting if I need to.

This gets ugly when the class has a lot of attributes because the string construction gets very long.

I can do...

return "foo: {0}\nbar: {1}\nbaz: {2}".format(self.foo,
self.bar,
self.baz)

which is an improvement, but there's still a very long line.

And there's also something like...

return "\n".join((": ".join((str(k), str(self.__dict__[k]))) for k in self.__dict__))

which is a nice length, but I lose control of the order of the attributes and the formatting is fixed. It also looks a bit too much like Lisp ;o)

Is there a better way?

Cheers,

Drea

p.s. I may want to substitute __repr__ for __str__ perhaps?
--
http://mail.python.org/mailman/listinfo/python-list


rosuav at gmail

May 10, 2012, 7:33 AM

Post #2 of 17 (1404 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On Thu, May 10, 2012 at 11:33 PM, Andreas Tawn <andreas.tawn [at] ubisoft> wrote:
> Say I've got a class...
>
> class test(object):
>    def __init__(self):
>        self.foo = 1
>        self.bar = 2
>        self.baz = 3
>
> I can say...
>
> def __str__(self):
>   return "foo: {0}\nbar: {1}\nbaz: {2}".format(self.foo, self.bar, self.baz)

This might be of use:

return """foo: {foo}
bar: {bar}
baz: {baz}""".format(**self.__dict__)

You're repeating yourself a bit, but this allows the labels to differ
from the format tags. If you're certain that you don't need that
flexibility, you could generate the format string dynamically:

return "\n".join(x+": {"+x+"}" for x in
("foo","bar","baz")).format(**self.__dict__)

That scales more nicely as the number of elements desired increases
(while still being 100% explicit - the presence and order of elements
is governed by the tuple), but is a bit inflexible and complicated.
I'd be inclined toward the triple-quoted-string one.

Tim Toady.

ChrisA
--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 10, 2012, 8:15 AM

Post #3 of 17 (1404 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> On Thu, May 10, 2012 at 11:33 PM, Andreas Tawn <andreas.tawn [at] ubisoft>
> wrote:
> > Say I've got a class...
> >
> > class test(object):
> >    def __init__(self):
> >        self.foo = 1
> >        self.bar = 2
> >        self.baz = 3
> >
> > I can say...
> >
> > def __str__(self):
> >   return "foo: {0}\nbar: {1}\nbaz: {2}".format(self.foo, self.bar,
> > self.baz)
>
> This might be of use:
>
> return """foo: {foo}
> bar: {bar}
> baz: {baz}""".format(**self.__dict__)
>
> You're repeating yourself a bit, but this allows the labels to differ from the format
> tags. If you're certain that you don't need that flexibility, you could generate the
> format string dynamically:
>
> return "\n".join(x+": {"+x+"}" for x in
> ("foo","bar","baz")).format(**self.__dict__)
>
> That scales more nicely as the number of elements desired increases (while still
> being 100% explicit - the presence and order of elements is governed by the tuple),
> but is a bit inflexible and complicated.
> I'd be inclined toward the triple-quoted-string one.

I considered the triple quote string one, but it's not very PEP 8 compatible in a real class because it includes the indentation in the formatted string.

To make it print properly, it has to look like this...

def __str__(self):
return """foo: {foo}
bar: {bar}
baz: {baz}""".format(**self.__dict__)

I didn't realise I could do implicit line continuation inside a list comprehension. That might be the best way.

Even more so with continuation lines inside the attribute name tuple.

def __str__(self):
return "\n".join(x+": {"+x+"}" for x in
("foo",
"bar",
"baz")).format(**self.__dict__)

Still feels a bit icky though.
--
http://mail.python.org/mailman/listinfo/python-list


rosuav at gmail

May 10, 2012, 2:47 PM

Post #4 of 17 (1398 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On Fri, May 11, 2012 at 1:15 AM, Andreas Tawn <andreas.tawn [at] ubisoft> wrote:
> I considered the triple quote string one, but it's not very PEP 8 compatible in a real class because it includes the indentation in the formatted string.

Yeah, that is an annoying side effect. My personal view is that code
should be written concisely, even if that means violating indentation.
One possible way around that is to break out the format string to a
module variable (thus completely unindented); another way is to accept
the indentation, for instance:

def __str__(self):
return """test():
foo: {foo}
bar: {bar}
baz: {baz}""".format(**self.__dict__)

resulting in a string that has a somewhat Pythonic syntax to it. Would
work nicely if you have a few "primary" attributes that go onto the
first line, and a bunch of "secondary" attributes that get listed
below.

ChrisA
--
http://mail.python.org/mailman/listinfo/python-list


cs at zip

May 10, 2012, 5:15 PM

Post #5 of 17 (1398 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On 10May2012 15:33, Andreas Tawn <andreas.tawn [at] ubisoft> wrote:
| Say I've got a class...
|
| class test(object):
| def __init__(self):
| self.foo = 1
| self.bar = 2
| self.baz = 3
|
| I can say...
|
| def __str__(self):
| return "foo: {0}\nbar: {1}\nbaz: {2}".format(self.foo, self.bar, self.baz)
|
| and everything's simple and clean and I can vary the formatting if I need to.
|
| This gets ugly when the class has a lot of attributes because the string construction gets very long.

This issue bit me once too often a few months ago, and now I have a
class called "O" from which I often subclass instead of from "object".
Its main purpose is a friendly __str__ method, though it also has a
friendly __init__.

Code:

class O(object):
''' A bare object subclass to allow storing arbitrary attributes.
It also has a nicer default str() action, and an aggressive repr().
'''

def __init__(self, **kw):
''' Initialise this O.
Fill in attributes from any keyword arguments if supplied.
This call can be omitted in subclasses if desired.
'''
for k in kw:
setattr(self, k, kw[k])

def __str__(self):
return ( "<%s %s>"
% ( self.__class__.__name__,
",".join([. "%s=%s" % (attr, getattr(self, attr))
for attr in sorted(dir(self)) if attr[0].isalpha()
])
)
)

So I have some code thus:

from cs.misc import O
......
class FilterModes(O):
def __init__(self, **kw):
# special case one parameter
self._maildb_path = kw.pop('maildb_path')
self._maildb_lock = allocate_lock()
O.__init__(self, **kw)
......
filter_modes = FilterModes(justone=justone,
delay=delay,
no_remove=no_remove,
no_save=no_save,
maildb_path=os.environ['MAILDB'],
maildir_cache={})


This removes a lot of guff from some common procedures.

Hope this helps,
--
Cameron Simpson <cs [at] zip> DoD#743
http://www.cskk.ezoshosting.com/cs/

There's a fine line between pathos and pathetic.
- David Stivers stiv [at] stat DoD #857
--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 11, 2012, 4:16 AM

Post #6 of 17 (1399 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> This issue bit me once too often a few months ago, and now I have a class called
> "O" from which I often subclass instead of from "object".
> Its main purpose is a friendly __str__ method, though it also has a friendly __init__.
>
> Code:
>
> class O(object):
> ''' A bare object subclass to allow storing arbitrary attributes.
> It also has a nicer default str() action, and an aggressive repr().
> '''
>
> def __init__(self, **kw):
> ''' Initialise this O.
> Fill in attributes from any keyword arguments if supplied.
> This call can be omitted in subclasses if desired.
> '''
> for k in kw:
> setattr(self, k, kw[k])
>
> def __str__(self):
> return ( "<%s %s>"
> % ( self.__class__.__name__,
> ",".join([. "%s=%s" % (attr, getattr(self, attr))
> for attr in sorted(dir(self)) if attr[0].isalpha()
> ])
> )
> )

This is a very interesting solution.

I think it might be better suited (for my purpose) to __repr__ rather than __str__, mostly because I still lose control of the order the attributes appear.

I really like the general idea of subclassing object though, because I often have classes with dozens of attributes and __init__ gets very messy.

Chris' dynamically generated format string looks to be my best bet in the absence of a perfect solution.

Cheers,

Drea
--
http://mail.python.org/mailman/listinfo/python-list


d at davea

May 11, 2012, 4:58 AM

Post #7 of 17 (1392 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On 05/11/2012 07:16 AM, Andreas Tawn wrote:
>> <SNIP>
>> This is a very interesting solution.
>>
>> I think it might be better suited (for my purpose) to __repr__ rather than __str__, mostly because I still lose control of the order the attributes appear.

I have no idea why using __repr__ versus __str__ would make any
difference in the order of the attributes. They're going to come out in
the order you specify, regardless of what you name your method. If you
don't like the arbitrary order you get from the dictionary, then either
sort it, or provide an explicit list.



--

DaveA

--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 11, 2012, 7:32 AM

Post #8 of 17 (1392 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> I have no idea why using __repr__ versus __str__ would make any difference in the
> order of the attributes. They're going to come out in the order you specify,
> regardless of what you name your method. If you don't like the arbitrary order you
> get from the dictionary, then either sort it, or provide an explicit list.

Only that, as the docs say, __repr__ should represent the entire object that could be used to build a new object with the same value. For that task the order of the attributes is immaterial.

I want __str__ to give me something easily readable and ordered in a way that's conveys some meaning about the attributes. It's also helpful to not have to display every attribute, of which there may be dozens.
--
http://mail.python.org/mailman/listinfo/python-list


breamoreboy at yahoo

May 11, 2012, 7:40 AM

Post #9 of 17 (1395 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On 11/05/2012 15:32, Andreas Tawn wrote:

> It's also helpful to not have to display every attribute, of which there may be dozens.

Do I detect a code smell here?

--
Cheers.

Mark Lawrence.

--
http://mail.python.org/mailman/listinfo/python-list


jeanmichel at sequans

May 11, 2012, 8:09 AM

Post #10 of 17 (1401 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

Mark Lawrence wrote:
> On 11/05/2012 15:32, Andreas Tawn wrote:
>
>> It's also helpful to not have to display every attribute, of which
>> there may be dozens.
>
> Do I detect a code smell here?
>
I think so, Murphy's law dictates that the attribute you're interested
in will not be displayed anyway.

JM
--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 11, 2012, 8:10 AM

Post #11 of 17 (1399 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> > It's also helpful to not have to display every attribute, of which there may be
> dozens.
>
> Do I detect a code smell here?

Possibly. I'll often try to subdivide into several simpler types, but sometimes that makes the code more complex than it needs to be.
--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 11, 2012, 8:24 AM

Post #12 of 17 (1394 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> >> It's also helpful to not have to display every attribute, of which
> >> there may be dozens.
> >
> > Do I detect a code smell here?
> >
> I think so, Murphy's law dictates that the attribute you're interested in will not be
> displayed anyway.

That's what __repr__'s for.
--
http://mail.python.org/mailman/listinfo/python-list


cs at zip

May 11, 2012, 3:44 PM

Post #13 of 17 (1359 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On 11May2012 15:40, Mark Lawrence <breamoreboy [at] yahoo> wrote:
| On 11/05/2012 15:32, Andreas Tawn wrote:
| > It's also helpful to not have to display every attribute, of which there may be dozens.
|
| Do I detect a code smell here?

Not necessarily. (Well, yeah, "dozens" may indicate time to partition
stuff.) My "O" class (back in the thread) deliberately shows only the
[a-z]* attributes so the user gets the public object state without the
noise of private attributes.

For __repr__ I might print everything. Haven't gone that far yet.

Cheers,
--
Cameron Simpson <cs [at] zip> DoD#743
http://www.cskk.ezoshosting.com/cs/

That is 27 years ago, or about half an eternity in computer years.
- Alan Tibbetts
--
http://mail.python.org/mailman/listinfo/python-list


zahlman at gmail

May 12, 2012, 5:36 AM

Post #14 of 17 (1355 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On Thu, May 10, 2012 at 9:33 AM, Andreas Tawn <andreas.tawn [at] ubisoft> wrote:
> And there's also something like...
>
> return "\n".join((": ".join((str(k), str(self.__dict__[k]))) for k in self.__dict__))
>
> which is a nice length, but I lose control of the order of the attributes and the formatting is fixed. It also looks a bit too much like Lisp ;o)
>
> Is there a better way?

If you don't care about being able to change the attributes
dynamically, define the `__slots__` of your class, and then you can
use

return '\n'.join('%s: %s' % (name, getattr(self, name)) for name in
self.__slots__)

Calling `getattr` is probably more Pythonic than looking things up in
`__dict__`, string formatting can take care of "casting" (converting,
really) for you, and the nested `join` is really overkill :) Anyway
the main point is that `__slots__` is a list, and thus has a defined
order.

>
> p.s. I may want to substitute __repr__ for __str__ perhaps?

Depending. Sometimes you want them to behave the same way. Since
functions are objects, this is as simple as `__repr__ = __str__`. :)

p.s. Is Python seeing a lot of use at Ubisoft or is this just for
personal interest (or perhaps both)?

--
~Zahlman {:>
--
http://mail.python.org/mailman/listinfo/python-list


ethan at stoneleaf

May 12, 2012, 6:10 AM

Post #15 of 17 (1360 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

Karl Knechtel wrote:
> On Thu, May 10, 2012 at 9:33 AM, Andreas Tawn <andreas.tawn [at] ubisoft> wrote:
>> And there's also something like...
>>
>> return "\n".join((": ".join((str(k), str(self.__dict__[k]))) for k in self.__dict__))
>>
>> which is a nice length, but I lose control of the order of the attributes and the formatting is fixed. It also looks a bit too much like Lisp ;o)
>>
>> Is there a better way?
>
> If you don't care about being able to change the attributes
> dynamically, define the `__slots__` of your class [...]

Firstly, __slots__ is a tuple.

Secondly, this is bad advice. __slots__ is there as a memory
optimization for classes that will have thousands of instances and do
not need the ability to have arbitrary attributes added after creation.
__slots__ is an advanced feature, and as such is easier to get wrong.
It is *not* there to make __str__ nor __repr__ easier to write.

8<-----------------------------------------------------------------------
Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class Test1(object):
... __slots__ = ('item', 'size')
... desc = 'class-only attribute'
...

>>> t1 = Test1()
>>> t1.desc = 'read-only attribute' # fails because 'desc' is
# not in __slots__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test1' object attribute 'desc' is read-only

>>> print t1.item # fails because 'item' was
# not set
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: item

>>> class Test2(Test1):
... def __init__(self, price):
... self.price = price
...
>>> t2 = Test2(7.99) # __slots__ not defined in
# subclass, optimizations lost

>>> t2.oops = '__slots__ no longer active'
>>> print t2.oops
__slots__ no longer active
8<-----------------------------------------------------------------------

~Ethan~
--
http://mail.python.org/mailman/listinfo/python-list


andreas.tawn at ubisoft

May 14, 2012, 2:16 AM

Post #16 of 17 (1352 views)
Permalink
RE: Dealing with the __str__ method in classes with lots of attributes [In reply to]

> p.s. Is Python seeing a lot of use at Ubisoft or is this just for personal interest (or
> perhaps both)?

We do use Python a fair bit, mostly for build systems and data mining, but also because it's the built-in script language for Motionbuilder.
--
http://mail.python.org/mailman/listinfo/python-list


zahlman at gmail

May 14, 2012, 10:15 AM

Post #17 of 17 (1361 views)
Permalink
Re: Dealing with the __str__ method in classes with lots of attributes [In reply to]

On Sat, May 12, 2012 at 9:10 AM, Ethan Furman <ethan [at] stoneleaf> wrote:
>
> Firstly, __slots__ is a tuple.

I object: conceptually, the "slots" of a class are set in stone, but
the `__slots__` attribute of a class object is just an attribute, and
any iterable (as long as it yields valid identifier names) can be used
when the `__slots__` magic is invoked in the class definition. FWIW,
all the ordinary examples I've seen use a list, although a tuple
arguably makes more sense.

Observe:

>>> class Evil(object):
... __slots__ = ('a%s' % a for a in range(10))
...
>>> Evil().__slots__
<generator object <genexpr> at 0x01EDFAA8>
>>> list(Evil().__slots__)
[] # the generator was consumed by the metaclass during class creation
>>> dir(Evil())
[.'__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__has
h__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__rep
r__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '
a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9'] # yep, the
expected attributes are there, and can be set.
>>> Evil.__slots__ = 42
# no exception

>
> Secondly, this is bad advice.  __slots__ is there as a memory optimization
> for classes that will have thousands of instances and do not need the
> ability to have arbitrary attributes added after creation.

I did explicitly indicate the latter part.

>  __slots__ is an
> advanced feature, and as such is easier to get wrong.  It is *not* there to
> make __str__ nor __repr__ easier to write.

Of course not, but it seems to me that it serves well in this particular case.

>>>> class Test1(object):
> ...     __slots__ = ('item', 'size')
> ...     desc = 'class-only attribute'
> ...
>
>>>> t1 = Test1()
>>>> t1.desc = 'read-only attribute'      # fails because 'desc' is
>                                         # not in __slots__

Well, sure; expecting to modify a class attribute via an instance is a
bit naughty anyway.

>>>> print t1.item                        # fails because 'item' was
>                                         # not set

Well, yes, that's what `__init__` is for; the same line would fail
without `__slots__` and for the same reason. Arguably, this is a
gotcha for people coming from C-like languages who are expecting
`__slots__` to make the class behave as if it had a defined layout,
but there isn't actually any more work involved here.

>>>> class Test2(Test1):
> ...     def __init__(self, price):
> ...         self.price = price
> ...
>>>> t2 = Test2(7.99)          # __slots__ not defined in
>                              # subclass, optimizations lost

Well, yes, but we aren't using it for the optimizations here!

But I see your point; explicit is better than implicit, and our
explicit purpose here is to have an explicit list of the attributes
we're interested in for __str__/__repr__ - which could be any other
named class attribute, without magic associated with it. That said,
`__slots__` is as close to a canonical listing of instance-specific
attributes as we have (`dir()` clearly won't cut it, as we don't want
methods or other class-specific stuff).

--
~Zahlman {:>
--
http://mail.python.org/mailman/listinfo/python-list

Python python RSS feed   Index | Next | Previous | View Threaded
 
 


Interested in having your list archived? Contact Gossamer Threads
 
  Web Applications & Managed Hosting Powered by Gossamer Threads Inc.