Dealing with the __str__ method in classes with lots of attributes

Karl Knechtel zahlman at gmail.com
Mon May 14 13:15:28 EDT 2012


On Sat, May 12, 2012 at 9:10 AM, Ethan Furman <ethan at stoneleaf.us> 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 {:>



More information about the Python-list mailing list