Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does inheritance of __slots__ in subclasses actually work?

In the Python data model reference section on slots there is a list of notes on using __slots__. I am thoroughly confused by the 1st and 6th items, because they seem to be contradicting each other.

First item:

  • When inheriting from a class without __slots__, the __dict__ attribute of that class will always be accessible, so a __slots__ definition in the subclass is meaningless.

Sixth item:

  • The action of a __slots__ declaration is limited to the class where it is defined. As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).

It seems to me these items could be better worded or shown through code, but I have been trying to wrap my head around this and am still coming up confused. I do understand how __slots__ are supposed to be used, and I am trying to get a better grasp on how they work.

The Question:

Can someone please explain to me in plain language what the conditions are for inheritance of slots when subclassing?

(Simple code examples would be helpful but not necessary.)

like image 902
jathanism Avatar asked Nov 29 '09 19:11

jathanism


People also ask

What is the __ slots __ attribute used in a class for?

__slots__ is a class variable. If you have more than one instance of your class, any change made to __slots__ will show up in every instance. You cannot access the memory allocated by the __slots__ declaration by using subscription. You will get only what is currently stored in the list.

What does __ slots __ mean in Python?

The special attribute __slots__ allows you to explicitly state which instance attributes you expect your object instances to have, with the expected results: faster attribute access.

Can you inherit from a subclass?

A subclass inherits all of the public and protected members of its parent, no matter what package the subclass is in. If the subclass is in the same package as its parent, it also inherits the package-private members of the parent.

How does class inheritance work?

Inheritance allows us to define a class that inherits all the methods and properties from another class. Parent class is the class being inherited from, also called base class. Child class is the class that inherits from another class, also called derived class.


2 Answers

As others have mentioned, the sole reason for defining __slots__ is to save some memory, when you have simple objects with a predefined set of attributes and don't want each to carry around a dictionary. This is meaningful only for classes of which you plan to have many instances, of course.

The savings may not be immediately obvious -- consider...:

>>> class NoSlots(object): pass ...  >>> n = NoSlots() >>> class WithSlots(object): __slots__ = 'a', 'b', 'c' ...  >>> w = WithSlots() >>> n.a = n.b = n.c = 23 >>> w.a = w.b = w.c = 23 >>> sys.getsizeof(n) 32 >>> sys.getsizeof(w) 36 

From this, it would seem the with-slots size is larger than the no-slots size! But that's a mistake, because sys.getsizeof doesn't consider "object contents" such as the dictionary:

>>> sys.getsizeof(n.__dict__) 140 

Since the dict alone takes 140 bytes, clearly the "32 bytes" object n is alleged to take are not considering all that's involved in each instance. You can do a better job with third-party extensions such as pympler:

>>> import pympler.asizeof >>> pympler.asizeof.asizeof(w) 96 >>> pympler.asizeof.asizeof(n) 288 

This shows much more clearly the memory footprint that's saved by __slots__: for a simple object such as this case, it's a bit less than 200 bytes, almost 2/3 of the object's overall footprint. Now, since these days a megabyte more or less doesn't really matter all that much to most applications, this also tells you that __slots__ is not worth the bother if you're going to have just a few thousand instances around at a time -- however, for millions of instances, it sure does make a very important difference. You can also get a microscopic speedup (partly due to better cache use for small objects with __slots__):

$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x' 10000000 loops, best of 3: 0.37 usec per loop $ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x' 1000000 loops, best of 3: 0.604 usec per loop $ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45' 1000000 loops, best of 3: 0.28 usec per loop $ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45' 1000000 loops, best of 3: 0.332 usec per loop 

but this is somewhat dependent on Python version (these are the numbers I measure repeatably with 2.5; with 2.6, I see a larger relative advantage to __slots__ for setting an attribute, but none at all, indeed a tiny disadvantage, for getting it).

Now, regarding inheritance: for an instance to be dict-less, all classes up its inheritance chain must also have dict-less instances. Classes with dict-less instances are those which define __slots__, plus most built-in types (built-in types whose instances have dicts are those on whose instances you can set arbitrary attributes, such as functions). Overlaps in slot names are not forbidden, but they're useless and waste some memory, since slots are inherited:

>>> class A(object): __slots__='a' ...  >>> class AB(A): __slots__='b' ...  >>> ab=AB() >>> ab.a = ab.b = 23 >>>  

as you see, you can set attribute a on an AB instance -- AB itself only defines slot b, but it inherits slot a from A. Repeating the inherited slot isn't forbidden:

>>> class ABRed(A): __slots__='a','b' ...  >>> abr=ABRed() >>> abr.a = abr.b = 23 

but does waste a little memory:

>>> pympler.asizeof.asizeof(ab) 88 >>> pympler.asizeof.asizeof(abr) 96 

so there's really no reason to do it.

like image 170
Alex Martelli Avatar answered Oct 26 '22 01:10

Alex Martelli


class WithSlots(object):     __slots__ = "a_slot"  class NoSlots(object):       # This class has __dict__     pass 

First Item

class A(NoSlots):            # even though A has __slots__, it inherits __dict__     __slots__ = "a_slot"     # from NoSlots, therefore __slots__ has no effect 

Sixth Item

class B(WithSlots):          # This class has no __dict__     __slots__ = "some_slot"  class C(WithSlots):          # This class has __dict__, because it doesn't     pass                     # specify __slots__ even though the superclass does. 

You probably won't need to use __slots__ in the near future. It's only intended to save memory at the cost of some flexibility. Unless you have tens of thousands of objects it won't matter.

like image 40
Georg Schölly Avatar answered Oct 26 '22 00:10

Georg Schölly