Python:在子类中inheritance__slots__是如何工作的?

在插槽上的Python数据模型参考部分中,有一个使用__slots__的注释列表。 我对第一和第六项感到十分困惑,因为它们似乎是相互矛盾的。

第一项:

  • 从没有__slots__的类inheritance时,该类的__dict__属性总是可访问的,所以子类中的__slots__定义是没有意义的。

第六项:

  • __slots__声明的操作仅限于定义的类。 因此,子类将有一个__dict__除非它们也定义了__slots__ (它只能包含任何附加槽的名称)。

在我看来,这些项目可能更好措辞或通过代码显示,但我一直试图围绕这一点,我仍然感到困惑。 我明白__slots__是如何被使用的 ,我正试图更好地把握他们的工作方式。

问题:

有人可以用简单的语言向我解释什么条件是子类inheritance的插槽?

(简单的代码示例将是有用的,但不是必要的。)

正如其他人所提到的那样,定义__slots__的唯一原因是保存一些内存,当你有简单的对象具有一组预定义的属性并且不希望每个对象都带着一个字典时。 当然,这对于你打算拥有多个实例的类来说是有意义的。

节省可能不会立即明显 – 考虑…:

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

由此看来,带槽的尺寸似乎大于无槽尺寸! 但这是一个错误,因为sys.getsizeof不考虑“对象内容”,如字典:

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

由于字典本身需要140个字节,显然“32字节”的对象n据称并不考虑每个实例涉及的所有内容。 您可以更好地使用pympler等第三方扩展:

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

这更清楚地显示了由__slots__保存的内存占用情况:对于一个简单的对象,比如这种情况,它比200个字节less了几乎2/3的对象的总占用空间。 现在,从现在开始,对于大多数应用程序,或多或less对大部分应用程序来说并不重要,这也告诉你,如果每次只有几千个实例, __slots__是不值得的,然而,对于数百万的例子来说,这确实会产生非常重要的影响。 你也可以得到一个微观的加速(部分原因是用__slots__更好地caching了小对象的使用):

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

但是这在某种程度上依赖于Python版本(这些是我用2.5来重复测量的数字;对于2.6来说,我认为__slots__设置一个属性有较大的相对优势,但是对于获取它而言确实是一个很小的优势) 。

现在,关于inheritance:对于一个实例来说,它的inheritance链上的所有类都必须有无字节的实例。 无dict实例的类是定义__slots__ ,加上大多数内置types(实例有dicts的内置types是在其实例上可以设置任意属性的types,如函数)。 在插槽名称重叠不禁止,但它们是无用的,浪费了一些内存,因为插槽是inheritance:

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

如你所见,你可以在AB实例上设置属性aAB本身只定义了槽b ,但它从Ainheritance槽a 。 重复inheritance的插槽不被禁止:

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

但确实浪费了一点记忆:

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

所以真的没有理由去做

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

第一项

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

第六项

 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. 

你可能在不久的将来不需要使用__slots__ 。 它只是为了节省内存,而不是牺牲一些灵活性。 除非你有成千上万的物体,否则无所谓。

从你链接的答案:

正确使用__slots__是为了节省对象的空间。 而不是有一个dynamic字典…

“从__slots__类inheritance时,该类的__dict__属性总是可以访问的”,所以添加自己的__slots__不能阻止对象具有__dict__ ,并且不能节省空间。

关于__slots__没有被inheritance的一点是一点点的钝。 请记住,这是一个神奇的属性,并不像其他属性,然后重新读取,说这个魔术槽行为不被inheritance。 (这真的就是这一切。)

我的理解如下:

  • X没有__dict__ <-------> X类,它的超类都有__slots__指定

  • 在这种情况下,类的实际插槽由X和它的超类的__slots__声明的联合组成; 如果这个联合不是不相交的行为是未定义的(并且将成为一个错误)

Python:在子类中inheritance__slots__是如何工作的?

我对第一和第六项感到十分困惑,因为它们似乎是相互矛盾的。

这些项目实际上并不矛盾。 首先考虑没有实现__slots__的类的子类,第二个考虑实现__slots__的类的子类。

没有实现__slots__的类的子类

我越来越意识到,正如Python文档(正确)所声称的那样,它们并不是完美的,特别是关于语言中较less使用的特性。 我会改变文档如下:

从没有__slots__的类inheritance时,该类的__dict__属性总是可访问的,所以子类中的__slots__定义是没有意义的

__slots__对于这样的class级仍然有意义。 它logging了类的属性的预期名称。 它也为这些属性创build插槽 – 它们将获得更快的查找并使用更less的空间。 它只是允许其他属性,这将被分配给__dict__

实现__slots__的类的子类

__slots__声明的操作仅限于定义的类。 因此,子类将有一个__dict__除非它们也定义了__slots__ (它只能包含任何附加槽的名称)。

那也不完全正确。 __slots__声明的操作并不完全限于定义它的类。 例如,它们可能会影响多重inheritance。

我会改变:

对于定义__slots__的inheritance树中的类,除非子类还定义了__slots__ (它只能包含任何附加槽的名称),否则子类将具有__dict__

(关于__slots__更多信息, 请参阅我的答案 )