4
\$\begingroup\$

If classes provide __slots__ and at the same time inherit from another class also providing __slots__, there is no straightforward way to access and list each and every slot of the class. Especially when you consider chaining of inheritance.

I've written this meta-class that adds the __all_slots__ property, listing all unique slots:

class MetaSlotMachine(type):
    """Meta-class that adds the attribute `__all_slots__` to a class.

    `__all_slots__`  is a set that contains all unique slots of a class,
    including the ones that are inherited from parents.

    """
    def __init__(cls, name, bases, dictionary):
        super(MetaSlotMachine, cls).__init__(name, bases, dictionary)
        slots_iterator = (getattr(c, '__slots__', ()) for c in cls.__mro__)
        # `__slots__` might only be a single string, 
        # so we need to put the strings into a tuple.
        # `basestring` becomes just `str` in Python 3
        slots_converted = ((slots,) if isinstance(slots, basestring) else slots 
                                    for slots in slots_iterator)
        cls.__all_slots__ = set()
        cls.__all_slots__.update(*slots_converted)

Let's consider classes that inherit slots, like the following:

class HasSlots1(object):
    __metaclass__ = MetaSlotMachine   # Python 2 Syntax
    __slots__ = ['x', 'y']

class HasSlots2(HasSlots1):
    __slots__ = 'zz'

class HasSlots3(HasSlots2):
    __slots__ = ()

If we create and instance via

myslots = HasSlots3()

and ask for

myslots.__slots__

Python will return the empty tuple:

 ()

However, now we can get hold of all slots via

myslots.__all_slots__

which returns all inherited slots:

{'y', 'x', 'zz'}

Is there anything flawed with the design of the meta-class? What about listing all slots based on the __mro__ in _get_all_slots function?


EDIT: Revised version with gathering slots within the init function. Moreover, special care is taken about __slots__ that only consist of a single string, like __slots__ = '__weakref__'.

\$\endgroup\$
  • \$\begingroup\$ Your edit invalidated Gareth Rees' answer, so was against the rules. See For an iterative review, is it okay to edit my own question to include revised code?. However, the answer is deleted so I'm not sure now what the acceptable process is. \$\endgroup\$ – Veedrac Apr 2 '15 at 11:23
  • \$\begingroup\$ That was actually the reason why I edited it, otherwise his suggestions and what I found out by discussing with him would have been lost. \$\endgroup\$ – SmCaterpillar Apr 2 '15 at 12:00
  • \$\begingroup\$ I deleted my answer because it was no longer relevant. \$\endgroup\$ – Gareth Rees Apr 2 '15 at 12:42
1
\$\begingroup\$

This is a clever use of metaclasses - in a good way. The only thing I would change is to use set().union over update (this is debatable).

\$\endgroup\$
  • 1
    \$\begingroup\$ Why would one prefer mro() over __mro__? From the docs (docs.python.org/2/library/stdtypes.html#class.__mro__) I would assume that mro() is only supposed to be called once at class instantiation (to actually put the resolution order into __mro__). \$\endgroup\$ – SmCaterpillar Apr 3 '15 at 8:20
  • \$\begingroup\$ @SmCaterpillar Ack, that's the darndest thing. Every other __xxx__/xxx() pair exists with the dunder method as the overloadable private version, but with mro the dunder attribute is a cache. Dastardly indeed. That said, CPython does use mro() itself (though less often than __mro__) so neither seems to be more "correct". I guess inspect.getmro, which just returns cls.__mro__, might be the "canonical" solution, but nobody seems to know about it... \$\endgroup\$ – Veedrac Apr 3 '15 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.