【问题标题】:Usage of __slots__?__slots__ 的使用?
【发布时间】:2010-10-03 02:06:48
【问题描述】:

__slots__ 在 Python 中的用途是什么——尤其是关于我什么时候想使用它,什么时候不想使用它?

【问题讨论】:

    标签: python oop python-internals slots


    【解决方案1】:

    如果你要实例化很多(成百上千)同一个类的对象,你会想要使用__slots____slots__ 仅作为内存优化工具存在。

    强烈建议不要使用__slots__ 来约束属性创建。

    使用__slots__ 腌制对象将无法使用默认(最旧的)腌制协议;需要指定更高版本。

    python 的一些其他自省功能也可能受到不利影响。

    【讨论】:

    • 我在我的答案中演示了酸洗一个开槽的对象,并解决了你答案的第一部分。
    • 我明白你的意思,但插槽也提供更快的属性访问(正如其他人所说)。在这种情况下,您不需要“实例化同一类的大量(数百、数千)个对象” 以获得性能。相反,您需要大量 访问同一实例的相同(开槽)属性。 (如果我错了,请纠正我。)
    • 为什么“非常不鼓励”?我最近在寻找一种限制动态属性创建的方法。我发现了一些东西,但没有提到插槽。现在我阅读了有关插槽的信息,这似乎正是我之前所寻找的。使用槽来防止在运行时添加属性有什么问题?
    • @idclev463035818 我不认为这有什么不妥。
    【解决方案2】:

    引用Jacob Hallen:

    正确使用__slots__ 是为了节省对象空间。而不是拥有 允许随时向对象添加属性的动态字典, 有一个静态结构,创建后不允许添加。 [__slots__ 的使用消除了每个对象使用一个字典的开销。] 虽然这有时是一个有用的优化,但它完全可以 如果 Python 解释器足够动态以至于它会 仅当对象实际上有添加时才需要 dict。

    不幸的是,插槽有副作用。他们改变了行为 具有可被控制狂滥用的插槽的对象 和静态类型的小玩意儿。这很糟糕,因为控制狂应该 滥用元类,而静态类型的小伙伴们应该滥用 装饰器,因为在 Python 中,做某事应该只有一种明显的方式。

    在没有__slots__ 的情况下让 CPython 足够智能以处理节省空间是一项主要任务 承诺,这可能就是为什么它不在 P3k 的更改列表中(还)。

    【讨论】:

    • 我想看一些关于“静态类型”/装饰点的详细说明,没有贬义。引用缺席的第三方是没有帮助的。 __slots__ 没有解决与静态类型相同的问题。例如,在 C++ 中,不是成员变量的声明受到限制,而是将非预期类型(并且编译器强制执行)分配给该变量。我不宽恕使用__slots__,只是对对话感兴趣。谢谢!
    【解决方案3】:

    __slots__ 基本上没有用处。

    当您认为您可能需要__slots__ 时,您实际上想要使用LightweightFlyweight 设计模式。这些是您不再想使用纯 Python 对象的情况。相反,您需要一个类似 Python 对象的包装器来围绕数组、结构或 numpy 数组。

    class Flyweight(object):
    
        def get(self, theData, index):
            return theData[index]
    
        def set(self, theData, index, value):
            theData[index]= value
    

    类包装器没有属性——它只是提供作用于底层数据的方法。这些方法可以简化为类方法。实际上,它可以简化为仅对底层数据数组进行操作的函数。

    【讨论】:

    • 享元与__slots__有什么关系?
    • @oefe:我当然不明白你的问题。我可以引用我的回答,如果它有帮助“当您认为您可能需要 slots 时,您实际上想要使用 ... Flyweight 设计模式”。这就是享元与 slots 的关系。您有更具体的问题吗?
    • @oefe:享元和__slots__都是节省内存的优化技术。 __slots__ 显示了当您拥有许多对象以及享元设计模式时的好处。两者都解决了同样的问题。
    • 在内存消耗和速度方面使用插槽和使用享元之间是否有可用的比较?
    • 尽管享元在某些情况下确实很有用,不管你信不信,“当我创建无数对象时如何减少 Python 中的内存使用”的答案并不总是“不要使用 Python为你的无数物品。”有时__slots__ 确实是答案,正如 Evgeni 指出的那样,它可以作为一个简单的事后想法添加(例如,您可以先关注正确性,然后再添加性能)。
    【解决方案4】:

    每个 python 对象都有一个__dict__ 属性,它是一个包含所有其他属性的字典。例如当你输入self.attr时,python实际上是在做self.__dict__['attr']。正如您可以想象的那样,使用字典来存储属性需要一些额外的空间和时间来访问它。

    但是,当您使用__slots__ 时,为该类创建的任何对象都不会具有__dict__ 属性。相反,所有属性访问都是通过指针直接完成的。

    所以如果想要一个 C 风格的结构而不是一个完整的类,你可以使用__slots__ 来压缩对象的大小并减少属性访问时间。一个很好的例子是包含属性 x 和 y 的 Point 类。如果要获得很多积分,可以尝试使用__slots__ 以节省一些内存。

    【讨论】:

    • 不,定义了__slots__ 的类的实例不像 像C 风格的结构。有一个类级别的字典将属性名称映射到索引,否则将无法执行以下操作:class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1) 我真的认为应该澄清这个答案(如果你愿意,我可以这样做)。另外,我不确定instance.__hidden_attributes[instance.__class__[attrname]] 是否比instance.__dict__[attrname] 快。
    【解决方案5】:

    槽对于库调用非常有用,可以在进行函数调用时消除“命名方法调度”。这在 SWIG documentation 中有所提及。对于希望减少常用函数的函数开销的高性能库,使用插槽要快得多。

    现在这可能与 OP 问题没有直接关系。它更多地与构建扩展有关,而不是与在对象上使用 slots 语法有关。但它确实有助于完整了解插槽的使用情况及其背后的一些原因。

    【讨论】:

      【解决方案6】:

      类实例的属性有 3 个属性:实例、属性名称和属性值。

      常规属性访问中,实例充当字典,属性名称充当字典中查找值的键。

      实例(属性)-->值

      __slots__访问中,属性名充当字典,实例充当字典中查找值的键。

      属性(实例)-->值

      flyweight 模式中,属性的名称充当字典,值充当查找实例的字典中的键。

      属性(值)-->实例

      【讨论】:

      • 这是一个很好的分享,并且不适合对也建议flyweights的答案之一的评论,但它不是问题本身的完整答案。特别是(仅在问题的上下文中):为什么 Flyweight,以及“应该避免哪些情况......”__slots__?
      • @Merlyn Morgan-Graham,它作为选择的提示:常规访问、__slots__ 或享元。
      【解决方案7】:

      在 Python 中,__slots__ 的目的是什么?在哪些情况下应该避免这种情况?

      TLDR:

      特殊属性__slots__ 允许您明确说明您希望您的对象实例具有哪些实例属性,以及预期的结果:

      1. 更快属性访问。
      2. 节省空间在内存中。

      节省的空间来自

      1. 将值引用存储在插槽中,而不是 __dict__
      2. 如果父类拒绝创建 __dict____weakref__ 并且您声明 __slots__,则拒绝创建它们。

      快速注意事项

      小警告,您应该只在继承树中声明一次特定的插槽。例如:

      class Base:
          __slots__ = 'foo', 'bar'
      
      class Right(Base):
          __slots__ = 'baz', 
      
      class Wrong(Base):
          __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar
      

      当你犯了这个错误时,Python 不会反对(它可能应该),否则问题可能不会出现,但是你的对象会占用更多的空间。 Python 3.8:

      >>> from sys import getsizeof
      >>> getsizeof(Right()), getsizeof(Wrong())
      (56, 72)
      

      这是因为 Base 的槽描述符有一个与 Wrong 分开的槽。这通常不应该出现,但它可以:

      >>> w = Wrong()
      >>> w.foo = 'foo'
      >>> Base.foo.__get__(w)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: foo
      >>> Wrong.foo.__get__(w)
      'foo'
      

      最大的警告是多重继承 - 不能组合多个“具有非空槽的父类”。

      为适应此限制,请遵循最佳实践:将除一个或所有父级抽象之外的所有抽象分解出来,它们的具体类和您的新具体类将共同继承 - 给抽象空槽(就像抽象基标准库中的类)。

      有关示例,请参阅下面的多继承部分。

      要求:

      • 要将__slots__ 中命名的属性实际存储在槽中而不是__dict__ 中,类必须继承自object(在 Python 3 中是自动的,但在 Python 2 中必须是显式的)。

      • 为防止创建__dict__,您必须从object 继承,并且继承中的所有类都必须声明__slots__,并且它们都不能有'__dict__' 条目。

      如果您想继续阅读,这里有很多细节。

      为什么使用__slots__:更快的属性访问。

      Python 的创建者 Guido van Rossum,states,他实际上创建了__slots__ 以便更快地访问属性。

      证明访问速度明显显着加快是微不足道的:

      import timeit
      
      class Foo(object): __slots__ = 'foo',
      
      class Bar(object): pass
      
      slotted = Foo()
      not_slotted = Bar()
      
      def get_set_delete_fn(obj):
          def get_set_delete():
              obj.foo = 'foo'
              obj.foo
              del obj.foo
          return get_set_delete
      

      >>> min(timeit.repeat(get_set_delete_fn(slotted)))
      0.2846834529991611
      >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
      0.3664822799983085
      

      在 Ubuntu 上的 Python 3.5 中,时隙访问速度快了近 30%。

      >>> 0.3664822799983085 / 0.2846834529991611
      1.2873325658284342
      

      在 Windows 上的 Python 2 中,我测得它快了大约 15%。

      为什么使用__slots__:节省内存

      __slots__ 的另一个目的是减少每个对象实例占用的内存空间。

      My own contribution to the documentation clearly states the reasons behind this:

      使用__dict__ 节省的空间可能很大。

      SQLAlchemy attributes__slots__ 节省了大量内存。

      为了验证这一点,在 Ubuntu Linux 上使用 Python 2.7 的 Anaconda 发行版,带有 guppy.hpy(又名 heapy)和 sys.getsizeof,没有声明 __slots__ 的类实例的大小是 64 字节.这包括__dict__。再次感谢 Python 的惰性评估,__dict__ 在被引用之前显然不会被调用,但没有数据的类通常是无用的。当调用存在时,__dict__ 属性至少额外增加 280 个字节。

      相比之下,__slots__ 声明为 ()(无数据)的类实例只有 16 个字节,总共 56 个字节,其中一个项目在插槽中,64 个字节有两个。

      对于 64 位 Python,我说明了 Python 2.7 和 3.6 中的内存消耗(以字节为单位),对于 dict 在 3.6 中增长的每个点(除了 0、1、和2个属性):

             Python 2.7             Python 3.6
      attrs  __slots__  __dict__*   __slots__  __dict__* | *(no slots defined)
      none   16         56 + 272†   16         56 + 112† | †if __dict__ referenced
      one    48         56 + 272    48         56 + 112
      two    56         56 + 272    56         56 + 112
      six    88         56 + 1040   88         56 + 152
      11     128        56 + 1040   128        56 + 240
      22     216        56 + 3344   216        56 + 408     
      43     384        56 + 3344   384        56 + 752
      

      因此,尽管 Python 3 中的 dicts 更小,但我们看到 __slots__ 可以很好地扩展实例以节省内存,这也是您希望使用 __slots__ 的主要原因。

      为了我的笔记的完整性,请注意,在 Python 2 中,类名称空间中的每个插槽的一次性成本为 64 字节,在 Python 3 中为 72 字节,因为插槽使用属性等数据描述符,称为“成员” ”。

      >>> Foo.foo
      <member 'foo' of 'Foo' objects>
      >>> type(Foo.foo)
      <class 'member_descriptor'>
      >>> getsizeof(Foo.foo)
      72
      

      __slots__的演示:

      要拒绝创建__dict__,您必须继承object。在 Python 3 中,一切都是 object 的子类,但在 Python 2 中,您必须明确:

      class Base(object): 
          __slots__ = ()
      

      现在:

      >>> b = Base()
      >>> b.a = 'a'
      Traceback (most recent call last):
        File "<pyshell#38>", line 1, in <module>
          b.a = 'a'
      AttributeError: 'Base' object has no attribute 'a'
      

      或者子类化另一个定义__slots__的类

      class Child(Base):
          __slots__ = ('a',)
      

      现在:

      c = Child()
      c.a = 'a'
      

      但是:

      >>> c.b = 'b'
      Traceback (most recent call last):
        File "<pyshell#42>", line 1, in <module>
          c.b = 'b'
      AttributeError: 'Child' object has no attribute 'b'
      

      要在子类化槽对象时允许创建__dict__,只需将'__dict__' 添加到__slots__(注意槽是有序的,您不应重复父类中已有的槽):

      class SlottedWithDict(Child): 
          __slots__ = ('__dict__', 'b')
      
      swd = SlottedWithDict()
      swd.a = 'a'
      swd.b = 'b'
      swd.c = 'c'
      

      >>> swd.__dict__
      {'c': 'c'}
      

      或者你甚至不需要在你的子类中声明__slots__,你仍然会使用父类的槽,但不限制__dict__的创建:

      class NoSlots(Child): pass
      ns = NoSlots()
      ns.a = 'a'
      ns.b = 'b'
      

      还有:

      >>> ns.__dict__
      {'b': 'b'}
      

      但是,__slots__ 可能会导致多重继承问题:

      class BaseA(object): 
          __slots__ = ('a',)
      
      class BaseB(object): 
          __slots__ = ('b',)
      

      因为从具有两个非空槽的父类创建子类失败:

      >>> class Child(BaseA, BaseB): __slots__ = ()
      Traceback (most recent call last):
        File "<pyshell#68>", line 1, in <module>
          class Child(BaseA, BaseB): __slots__ = ()
      TypeError: Error when calling the metaclass bases
          multiple bases have instance lay-out conflict
      

      如果您遇到这个问题,您可以从父级中删除__slots__,或者如果您可以控制父级,则给它们空槽,或者重构抽象:

      from abc import ABC
      
      class AbstractA(ABC):
          __slots__ = ()
      
      class BaseA(AbstractA): 
          __slots__ = ('a',)
      
      class AbstractB(ABC):
          __slots__ = ()
      
      class BaseB(AbstractB): 
          __slots__ = ('b',)
      
      class Child(AbstractA, AbstractB): 
          __slots__ = ('a', 'b')
      
      c = Child() # no problem!
      

      '__dict__' 添加到__slots__ 以获得动态分配:

      class Foo(object):
          __slots__ = 'bar', 'baz', '__dict__'
      

      现在:

      >>> foo = Foo()
      >>> foo.boink = 'boink'
      

      因此,在插槽中使用'__dict__' 时,我们会失去一些大小优势,因为具有动态分配的好处,并且仍然为我们所期望的名称保留插槽。

      当您从未开槽的对象继承时,使用 __slots__ 时会获得相同的语义 - __slots__ 中的名称指向开槽值,而任何其他值都放在实例的__dict__.

      避免 __slots__ 因为您希望能够即时添加属性实际上不是一个好理由 - 如果需要,只需将 "__dict__" 添加到您的 __slots__ 即可。

      如果您需要该功能,同样可以将__weakref__ 显式添加到__slots__

      子类化命名元组时设置为空元组:

      namedtuple 内置的不可变实例非常轻量级(本质上是元组的大小),但要获得好处,如果您对它们进行子类化,您需要自己做:

      from collections import namedtuple
      class MyNT(namedtuple('MyNT', 'bar baz')):
          """MyNT is an immutable and lightweight object"""
          __slots__ = ()
      

      用法:

      >>> nt = MyNT('bar', 'baz')
      >>> nt.bar
      'bar'
      >>> nt.baz
      'baz'
      

      尝试分配意外属性会引发AttributeError,因为我们阻止了__dict__的创建:

      >>> nt.quux = 'quux'
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'MyNT' object has no attribute 'quux'
      

      可以通过省略__slots__ = () 来允许创建__dict__,但您不能将非空__slots__ 与元组的子类型一起使用。

      最大的警告:多重继承

      即使多个父母的非空插槽相同,它们也不能一起使用:

      class Foo(object): 
          __slots__ = 'foo', 'bar'
      class Bar(object):
          __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
      
      >>> class Baz(Foo, Bar): pass
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: Error when calling the metaclass bases
          multiple bases have instance lay-out conflict
      

      在父级中使用空的__slots__ 似乎提供了最大的灵活性,允许子级选择阻止或允许(通过添加'__dict__' 来获得动态分配,请参阅上面的部分) 创建__dict__

      class Foo(object): __slots__ = ()
      class Bar(object): __slots__ = ()
      class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
      b = Baz()
      b.foo, b.bar = 'foo', 'bar'
      

      你没有有插槽 - 所以如果你添加它们,然后再删除它们,应该不会造成任何问题。

      在这里冒险:如果您正在编写mixins 或使用abstract base classes,它们不打算被实例化,那么这些父母中的空__slots__ 似乎就子类的灵活性而言,这是最好的方法。

      为了演示,首先,让我们创建一个类,其中包含我们希望在多重继承下使用的代码

      class AbstractBase:
          __slots__ = ()
          def __init__(self, a, b):
              self.a = a
              self.b = b
          def __repr__(self):
              return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
      

      我们可以通过继承和声明预期的插槽直接使用上述内容:

      class Foo(AbstractBase):
          __slots__ = 'a', 'b'
      

      但我们不在乎,这是微不足道的单继承,我们需要另一个我们可能也继承自的类,可能带有嘈杂的属性:

      class AbstractBaseC:
          __slots__ = ()
          @property
          def c(self):
              print('getting c!')
              return self._c
          @c.setter
          def c(self, arg):
              print('setting c!')
              self._c = arg
      

      现在,如果两个基地都有非空插槽,我们就无法执行以下操作。 (事实上​​,如果我们愿意,我们可以给 AbstractBase 非空插槽 a 和 b,并将它们排除在下面的声明之外 - 将它们留在里面是错误的):

      class Concretion(AbstractBase, AbstractBaseC):
          __slots__ = 'a b _c'.split()
      

      现在我们通过多重继承获得了两者的功能,并且仍然可以拒绝 __dict____weakref__ 实例化:

      >>> c = Concretion('a', 'b')
      >>> c.c = c
      setting c!
      >>> c.c
      getting c!
      Concretion('a', 'b')
      >>> c.d = 'd'
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Concretion' object has no attribute 'd'
      

      避免槽的其他情况:

      • 当您想对另一个没有它们的类执行__class__ 赋值时避免使用它们(并且您不能添加它们),除非插槽布局相同。 (我非常有兴趣了解谁在这样做以及为什么这样做。)
      • 如果您想要子类化可变长度内置函数(如 long、tuple 或 str)并且想要向它们添加属性,请避免使用它们。
      • 如果您坚持通过类属性为实例变量提供默认值,请避免使用它们。

      您也许可以从 __slots__ documentation (the 3.7 dev docs are the most current) 的其余部分中梳理出进一步的警告,我最近对此做出了重要贡献。

      对其他答案的批评

      当前的热门答案引用了过时的信息,并且非常随意,并且在某些重要方面没有达到目标。

      不要“仅在实例化大量对象时使用__slots__

      我引用:

      “如果你要实例化很多(成百上千)同一个类的对象,你会想要使用__slots__。”

      例如来自collections 模块的抽象基类不会被实例化,但会为它们声明__slots__

      为什么?

      如果用户希望拒绝创建 __dict____weakref__,则这些内容不能在父类中可用。

      __slots__ 在创建接口或 mixin 时有助于提高可重用性。

      确实,许多 Python 用户并不是为了可重用性而编写的,但是当您这样做时,可以选择拒绝不必要的空间使用是很有价值的。

      __slots__ 不会破坏酸洗

      腌制开槽对象时,您可能会发现它抱怨带有误导性的TypeError

      >>> pickle.loads(pickle.dumps(f))
      TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
      

      这实际上是不正确的。此消息来自最旧的协议,这是默认协议。您可以使用 -1 参数选择最新的协议。在 Python 2.7 中,这将是 2(在 2.3 中引入),而在 3.6 中,它是 4

      >>> pickle.loads(pickle.dumps(f, -1))
      <__main__.Foo object at 0x1129C770>
      

      在 Python 2.7 中:

      >>> pickle.loads(pickle.dumps(f, 2))
      <__main__.Foo object at 0x1129C770>
      

      在 Python 3.6 中

      >>> pickle.loads(pickle.dumps(f, 4))
      <__main__.Foo object at 0x1129C770>
      

      所以我会记住这一点,因为这是一个已解决的问题。

      对(直到 2016 年 10 月 2 日)接受的答案的批评

      第一段一半是简短的解释,一半是预测性的。这是唯一真正回答问题的部分

      __slots__ 的正确使用是为了节省对象空间。与允许随时向对象添加属性的动态字典不同,有一个不允许在创建后添加的静态结构。这为每个使用插槽的对象节省了一个 dict 的开销

      后半段是一厢情愿,离题:

      虽然这有时是一个有用的优化,但如果 Python 解释器足够动态,以至于它只需要 dict 实际添加到对象时,这将是完全没有必要的。

      Python实际上做了类似的事情,只是在访问时创建__dict__,但是创建大量没有数据的对象是相当荒谬的。

      第二段过于简单化并且忽略了避免__slots__的实际原因。以下是不是避免插槽的真正原因(出于实际原因,请参阅上面我的其余答案。):

      它们以一种可能被控制狂和静态打字小鬼滥用的方式改变了具有插槽的对象的行为。

      然后继续讨论使用 Python 实现这一不正当目标的其他方法,而不讨论与 __slots__ 相关的任何事情。

      第三段更一厢情愿。总的来说,这些内容大多是回答者甚至没有创作的不合时宜的内容,并且为该网站的批评者提供了弹药。

      内存使用证据

      创建一些普通对象和开槽对象:

      >>> class Foo(object): pass
      >>> class Bar(object): __slots__ = ()
      

      实例化一百万个:

      >>> foos = [Foo() for f in xrange(1000000)]
      >>> bars = [Bar() for b in xrange(1000000)]
      

      检查guppy.hpy().heap():

      >>> guppy.hpy().heap()
      Partition of a set of 2028259 objects. Total size = 99763360 bytes.
       Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
           0 1000000  49 64000000  64  64000000  64 __main__.Foo
           1     169   0 16281480  16  80281480  80 list
           2 1000000  49 16000000  16  96281480  97 __main__.Bar
           3   12284   1   987472   1  97268952  97 str
      ...
      

      访问常规对象及其__dict__ 并再次检查:

      >>> for f in foos:
      ...     f.__dict__
      >>> guppy.hpy().heap()
      Partition of a set of 3028258 objects. Total size = 379763480 bytes.
       Index  Count   %      Size    % Cumulative  % Kind (class / dict of class)
           0 1000000  33 280000000  74 280000000  74 dict of __main__.Foo
           1 1000000  33  64000000  17 344000000  91 __main__.Foo
           2     169   0  16281480   4 360281480  95 list
           3 1000000  33  16000000   4 376281480  99 __main__.Bar
           4   12284   0    987472   0 377268952  99 str
      ...
      

      这与Python的历史是一致的,来自Unifying types and classes in Python 2.2

      如果您对内置类型进行子类化,则会自动将额外空间添加到实例中以容纳 __dict____weakrefs__。 (__dict__ 在您使用它之前不会初始化,因此您不必担心创建的每个实例的空字典占用的空间。)如果您不需要这个额外的空间,您可以添加短语“__slots__ = []”给你的班级。

      【讨论】:

      • 这个答案应该是关于__slots__的官方Python文档的一部分。严重地!谢谢!
      • @NightElfik 信不信由你,大约一年前,我在__slots__ 上为 Python 文档做出了贡献:github.com/python/cpython/pull/1819/files
      • 非常详细的答案。我有一个问题:是否应该将插槽用作默认值除非使用达到其中一个警告,或者如果你知道你将要为速度/内存而奋斗,那么插槽是否值得考虑?换句话说,您是否应该鼓励新手了解它们并从一开始就使用它们?
      • @pepoluan 不,您不需要在__slots__ 中列出方法名称 - 但感谢您的提问!插槽声明在命名空间(__dict__)中创建描述符对象,就像方法定义一样。
      • @greatvovan 感谢您引起我的注意,我已经更新了两个位置的文本以明确指出这一点。让我知道这是否很好,或者您是否认为我错过了任何其他地方或任何其他问题。非常感谢。
      【解决方案8】:

      除了其他答案,这里是一个使用__slots__的例子:

      >>> class Test(object):   #Must be new-style class!
      ...  __slots__ = ['x', 'y']
      ... 
      >>> pt = Test()
      >>> dir(pt)
      ['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', 
       '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', 
       '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
      >>> pt.x
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: x
      >>> pt.x = 1
      >>> pt.x
      1
      >>> pt.z = 2
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Test' object has no attribute 'z'
      >>> pt.__dict__
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Test' object has no attribute '__dict__'
      >>> pt.__slots__
      ['x', 'y']
      

      所以,要实现__slots__,只需要多一行(如果还没有的话,让你的类成为一个新样式的类)。通过这种方式,您可以reduce the memory footprint of those classes 5-fold,但在必要时必须编写自定义泡菜代码。

      【讨论】:

        【解决方案9】:

        最初的问题是关于一般用例,而不仅仅是关于内存。 所以这里应该提到的是,当实例化大量对象时,您也可以获得更好的性能 - 有趣的例如在将大型文档解析为对象或从数据库中解析时。

        这里是使用插槽和不使用插槽创建具有一百万个条目的对象树的比较。作为对树使用普通字典时的性能参考(OSX 上的 Py2.7.10):

        ********** RUN 1 **********
        1.96036410332 <class 'css_tree_select.element.Element'>
        3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
        2.90828204155 dict
        ********** RUN 2 **********
        1.77050495148 <class 'css_tree_select.element.Element'>
        3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
        2.84120798111 dict
        ********** RUN 3 **********
        1.84069895744 <class 'css_tree_select.element.Element'>
        3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
        2.59615707397 dict
        ********** RUN 4 **********
        1.75041103363 <class 'css_tree_select.element.Element'>
        3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
        2.70941114426 dict
        

        测试类(ident,appart from slots):

        class Element(object):
            __slots__ = ['_typ', 'id', 'parent', 'childs']
            def __init__(self, typ, id, parent=None):
                self._typ = typ
                self.id = id
                self.childs = []
                if parent:
                    self.parent = parent
                    parent.childs.append(self)
        
        class ElementNoSlots(object): (same, w/o slots)
        

        测试代码,详细模式:

        na, nb, nc = 100, 100, 100
        for i in (1, 2, 3, 4):
            print '*' * 10, 'RUN', i, '*' * 10
            # tree with slot and no slot:
            for cls in Element, ElementNoSlots:
                t1 = time.time()
                root = cls('root', 'root')
                for i in xrange(na):
                    ela = cls(typ='a', id=i, parent=root)
                    for j in xrange(nb):
                        elb = cls(typ='b', id=(i, j), parent=ela)
                        for k in xrange(nc):
                            elc = cls(typ='c', id=(i, j, k), parent=elb)
                to =  time.time() - t1
                print to, cls
                del root
        
            # ref: tree with dicts only:
            t1 = time.time()
            droot = {'childs': []}
            for i in xrange(na):
                ela =  {'typ': 'a', id: i, 'childs': []}
                droot['childs'].append(ela)
                for j in xrange(nb):
                    elb =  {'typ': 'b', id: (i, j), 'childs': []}
                    ela['childs'].append(elb)
                    for k in xrange(nc):
                        elc =  {'typ': 'c', id: (i, j, k), 'childs': []}
                        elb['childs'].append(elc)
            td = time.time() - t1
            print td, 'dict'
            del droot
        

        【讨论】:

          【解决方案10】:

          __slot__ 属性的一个非常简单的例子。

          问题:没有__slots__

          如果我的类中没有__slot__ 属性,我可以向我的对象添加新属性。

          class Test:
              pass
          
          obj1=Test()
          obj2=Test()
          
          print(obj1.__dict__)  #--> {}
          obj1.x=12
          print(obj1.__dict__)  # --> {'x': 12}
          obj1.y=20
          print(obj1.__dict__)  # --> {'x': 12, 'y': 20}
          
          obj2.x=99
          print(obj2.__dict__)  # --> {'x': 99}
          

          如果你看上面的例子,你可以看到 obj1obj2 有自己的 xy > 属性,python 还为每个对象(obj1obj2)创建了一个dict 属性。

          假设如果我的类 Test 有数千个这样的对象?为每个对象创建一个附加属性dict 会在我的代码中造成大量开销(内存、计算能力等)。

          解决方案:使用__slots__

          现在在以下示例中,我的类 Test 包含 __slots__ 属性。现在我无法向我的对象添加新属性(属性x 除外),python 不再创建dict 属性。这消除了每个对象的开销,如果您有很多对象,这可能会变得很重要。

          class Test:
              __slots__=("x")
          
          obj1=Test()
          obj2=Test()
          obj1.x=12
          print(obj1.x)  # --> 12
          obj2.x=99
          print(obj2.x)  # --> 99
          
          obj1.y=28
          print(obj1.y)  # --> AttributeError: 'Test' object has no attribute 'y'
          

          【讨论】:

            【解决方案11】:

            __slots__ 的另一个有点晦涩的用法是从 ProxyTypes 包(以前是 PEAK 项目的一部分)向对象代理添加属性。它的ObjectWrapper 允许您代理另一个对象,但拦截与代理对象的所有交互。它不是很常用(并且不支持 Python 3),但我们已经使用它来实现一个线程安全的阻塞包装器,该包装器围绕基于 tornado 的异步实现,该实现通过 ioloop 反弹对代理对象的所有访问,使用线程安全concurrent.Future 对象同步并返回结果。

            默认情况下,对代理对象的任何属性访问都会为您提供代理对象的结果。如果需要在代理对象上添加属性,可以使用__slots__

            from peak.util.proxies import ObjectWrapper
            
            class Original(object):
                def __init__(self):
                    self.name = 'The Original'
            
            class ProxyOriginal(ObjectWrapper):
            
                __slots__ = ['proxy_name']
            
                def __init__(self, subject, proxy_name):
                    # proxy_info attributed added directly to the
                    # Original instance, not the ProxyOriginal instance
                    self.proxy_info = 'You are proxied by {}'.format(proxy_name)
            
                    # proxy_name added to ProxyOriginal instance, since it is
                    # defined in __slots__
                    self.proxy_name = proxy_name
            
                    super(ProxyOriginal, self).__init__(subject)
            
            if __name__ == "__main__":
                original = Original()
                proxy = ProxyOriginal(original, 'Proxy Overlord')
            
                # Both statements print "The Original"
                print "original.name: ", original.name
                print "proxy.name: ", proxy.name
            
                # Both statements below print 
                # "You are proxied by Proxy Overlord", since the ProxyOriginal
                # __init__ sets it to the original object 
                print "original.proxy_info: ", original.proxy_info
                print "proxy.proxy_info: ", proxy.proxy_info
            
                # prints "Proxy Overlord"
                print "proxy.proxy_name: ", proxy.proxy_name
                # Raises AttributeError since proxy_name is only set on 
                # the proxy object
                print "original.proxy_name: ", proxy.proxy_name
            

            【讨论】:

              猜你喜欢
              • 2021-05-07
              • 1970-01-01
              • 2014-05-28
              • 2013-01-17
              • 2012-12-24
              • 1970-01-01
              • 1970-01-01
              • 2012-12-20
              相关资源
              最近更新 更多