LoginSignup
14
11

More than 5 years have passed since last update.

__slots__を定義する場合の継承に関する注意点

Posted at

通常、class文によって定義されたクラスのインスタンスは自由にデータメンバを追加することができ、属性の値は__dict__メンバ変数に保存されていますが、クラスに__slots__を定義し、明示的にデータメンバを列挙すると、そのクラスのインスタンスは、__dict__を持たなくなり、列挙されていないデータメンバを持てなくなります。
__dict__を持たない分、メモリが節約でき、またデータメンバへのアクセスも若干早くなります。
ただ、__slots__を定義する際、いくつか注意点があります。
(参考: https://docs.python.org/3/reference/datamodel.html#slots)

1. クラスに__slots__を定義する場合、その基底クラスは全て__slots__を持つ必要がある。

次の例のように、基底クラスに__slots__が定義されていないと、__dict__が生成されてデータメンバが追加可能になり、__slots__を定義した意味が無くなります。


class A:
    pass

class B(A):
    __slots__ = ('foo',)
>>> b = B()

>>> b.bar = 3     # __slots__を定義しているにも関わらず属性が定義できてしまう

>>> b.__dict__    # __dict__が生成されてしまっている
{'bar': 3}

基底クラスに__slots__を定義すると、この問題は解決します。


class A:
    __slots__ = ()    # 空の__slots__を定義する

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

>>> b = B()

>>> b.bar = 3     
AttributeError: 'B' object has no attribute 'bar'

>>> b.__dict__   
AttributeError: 'B' object has no attribute '__dict__'

2. 基底クラスの__slots__に同名の変数の定義があると、重複してデータメンバが確保されていまう。
無駄なアクセスできない変数が生成されてまいます。将来的にはエラーとなるようチェックをかけるかもしれないということですが、Python 3.7時点ではエラーにはなりません。

class A:
    __slots__ = ('foo',)

class B(A):
    __slots__ = ('foo',)
>>> import sys

>>> a = A()

>>> b = B()

>>> sys.getsizeof(a)
48

>>> sys.getsizeof(b)
56

3. 多重継承により、空でない__slots__を持つ複数の基底クラスは持てない。
この制約はエラーになるのですぐにわかりますが、下記のコードを実行するとTypeError: multiple bases have instance lay-out conflictとなります。

class A:
    __slots__ = ('foo',)

class B:
    __slots__ = ('bar',)

class C(A, B):
    __slots__ = ('baz',)

次のように、どちらか一方は空の__slots__となるようにクラスの設計を考え直す必要があります。

class A:
    __slots__ = ()

class B:
    __slots__ = ('foo', 'bar')

class C(A, B):
    __slots__ = ('baz',)

参考: https://stackoverflow.com/questions/472000/usage-of-slots

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11