Re: Correct type for a simple “bag of attributes” namespace object

Chris Angelico rosuav at gmail.com
Sun Aug 3 11:00:35 EDT 2014


On Mon, Aug 4, 2014 at 12:36 AM, Roy Smith <roy at panix.com> wrote:
> In article <mailman.12582.1407072928.18130.python-list at python.org>,
>  Chris Angelico <rosuav at gmail.com> wrote:
>
>> On Sun, Aug 3, 2014 at 11:25 PM, Roy Smith <roy at panix.com> wrote:
>> > in which case, I've said, "make Foos just like objects, except for, oh,
>> > never mind, there aren't any differences".  But, in reality, the system
>> > bolted on the ability to have user-defined attributes without telling
>> > me.  I don't think it's unreasonable to be surprised at that.
>>
>> I agree that this is slightly surprising. However, imagine if it were
>> the other way:
>>
>> class Foo(object):
>>     x = 1
>>     def __init__(self): self.y = 2
>>
>> These would throw errors, unless you explicitly disable __slots__
>> processing. When there's two ways to do things and both would be
>> surprising, you pick the one that's going to be less of a surprise, or
>> surprising less often, and accept it. That doesn't mean it's not a
>> problem, but it's better than the alternative.
>>
>> ChrisA
>
> I'm not following you at all.  What does "the other way" mean, and how
> would that cause the above to generate errors, and what does this have
> to do with __slots__?

Fact #1: Some classes, like object, don't have a dictionary for
arbitrary attributes. (I'll call this __slots__ mode, although it's
not technically __slots__when it's a C-implemented class.)

Fact #2: You can subclass such objects.

There are two ways that this subclassing could be done. Either it
maintains __slots__ mode, which means you can see every change (and
going "class Foo(Bar): pass" will make an exact subclass of Bar with
identical behaviour), or it drops __slots__ and adds a dictionary
unless you explicitly reenable __slots__.

>>> class Base(object): __slots__ = ('a', 'b', 'c')
>>> class Deriv(Base): pass
>>> Base().d = 1
Traceback (most recent call last):
  File "<pyshell#71>", line 1, in <module>
    Base().d = 1
AttributeError: 'Base' object has no attribute 'd'
>>> Deriv().d = 1

Python opted to go with the second behaviour: the subclass is not
bound to the superclass's __slots__, but gets a dictionary unless it
itself specifies __slots__ (in which case it gets all of them,
parent's included):

>>> class SlottedDeriv(Base): __slots__ = ('d', 'e', 'f')
>>> SlottedDeriv().a = 1
>>> SlottedDeriv().d = 1
>>> SlottedDeriv().g = 1
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    SlottedDeriv().g = 1
AttributeError: 'SlottedDeriv' object has no attribute 'g'

The alternative would be for __slots__ to normally copy down, and to
have to be explicitly removed - for "pass" to be actually equivalent
to this:

>>> class Deriv(Base): __slots__ = Base.__slots__
>>> Deriv().d = 1
Traceback (most recent call last):
  File "<pyshell#82>", line 1, in <module>
    Deriv().d = 1
AttributeError: 'Deriv' object has no attribute 'd'

and for some other syntax to do what "pass" currently does. That has
the benefit of purity (it's easy to describe what happens, there's no
magic going on), but at the expense of practicality (you'd have to
explicitly de-slottify your classes before you can add functionality
to them). And we know what the Zen of Python says about which of those
takes priority.

ChrisA



More information about the Python-list mailing list