19

I need to initialise all slots of an instance with None. How do I get all slots of a derived class?

Example (which does not work):

class A(object):
    __slots__ = "a"

    def __init__(self):
        # this does not work for inherited classes
        for slot in type(self).__slots__:
            setattr(self, slot, None)

class B(A):
    __slots__ = "b"

I could use an additional class attribute which holds the slots (including the inherited) for all classes, like

class A(object):
    __slots__ = "a"
    all_slots = "a"

    def __init__(self):
        # this does not work for inherited classes
        for slot in type(self).all_slots:
            setattr(self, slot, None)

class B(A):
    __slots__ = "b"
    all_slots = ["a", "b"]

but that seems suboptimal.

Any comments are appreciated!

Cheers,

Jan

Knack
  • 1,044
  • 2
  • 12
  • 25
  • 3
    I use these classes to handle JSON business items on the client ("tags": richt client, PyQt, attribute access via descriptors, lazy resolving of references, type validation). I have up to a million of those objects, so memory requirements might be an issue (even if I haven't proper real world data now -- I know what you're thinking ... root of all evil ... ;-)). But I also need to prohibit any dynamic attribute creation. This makes sure that there's not 'garbage' attributes when committing newly created objects back to the server. – Knack Jul 17 '11 at 10:26

2 Answers2

16

First of all, it's

class A(object):
    __slots__ = ('a',)
class B(A):
    __slots__ =  ('b',)

Making a list that contains all elements contained by __slots__ of B or any of its parent classes would be:

from itertools import chain
slots = chain.from_iterable(getattr(cls, '__slots__', []) for cls in B.__mro__)
Florian Mayer
  • 3,041
  • 24
  • 17
  • Thanks! That's nifty. And the first time I will use chain in my code! – Knack Jul 17 '11 at 08:45
  • 1
    This isn't quite right. It's possible for an attribute to appear in `__slots__` in multiple places, e.g. in the parent and the child. So the slots iterator that you produce can have multiple copies of the same attribute. May do something like `for cls in b.__class__.__mro__: slots.update(getattr(cls,"__slots__",[]))` where slots is some object like a set. – pythonic metaphor Aug 23 '11 at 16:00
  • 1
    Suggest replacing getattr(cls, '__slots__', []) with getattr(cls, '__slots__', tuple() ) -- since __slots__ tends to be used for lots of very small objects, the overhead of creating an additional list for the few cases where getattr returns [] may actually be high (though better to make a cls cache) – Michael Scott Asato Cuthbert May 17 '14 at 15:49
  • If there are more than one occurrence in the mro slots of the same attribute that's a code bug and should be fixed - OTH it may be worth the hassle to inverse the mro to get superclass slots first – Mr_and_Mrs_D Oct 31 '16 at 17:36
11

You want to iterate through each class in the MRO:

class A(object):
    __slots__ = ('x', 'y')
    def __init__(self):
        for slots in [getattr(cls, '__slots__', []) for cls in type(self).__mro__]:
            for attr in slots:
                setattr(self, attr, None)

You can see that this works as expected in the derived class:

class B(A):
    __slots__ = ('z',)

>>> b = B()

>>> b.x, b.y, b.z
<<< (None, None, None)
Zach Kelling
  • 52,505
  • 13
  • 109
  • 108