__slots__的用法?


Answers:


1017

在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)

这是因为基准站的插槽描述符的插槽与错误的插槽分开。通常不应该这样,但是可以:

>>> 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'

最大的警告是多重继承-无法将多个“具有非空插槽的父类”组合在一起。

为适应此限制,请遵循最佳做法:排除其父母(或他们的具体类)将共同继承的除一个或所有父代之外的所有抽象-给这些抽象留出空位(就像在父类中的抽象基类一样)标准库)。

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

要求:

  • 要使名为in的属性__slots__实际上存储在插槽中而不是存储在插槽中__dict__,则类必须从继承object

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

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

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

Python的创建者Guido van Rossum 指出,他实际上是__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 3.5上的Python 3.5中,插槽式访问的速度几乎快了30%。

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

在Windows上的Python 2中,我测得的速度要快15%。

为何使用__slots__:内存节省

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

我自己对文档的贡献清楚地说明了背后的原因

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

SQLAlchemy将大量内存节省归因__slots__

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

相反,__slots__声明为()(无数据)的类实例只有16个字节,插槽中有一项的总字节数为56个,插槽中有两项的总数为64个字节。

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

       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中的指令较小,但我们仍然可以看到__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

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时,设置为空tuple:

内置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'

可以__dict__通过设置off 允许创建__slots__ = (),但是不能__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'

你不具备有槽-因此,如果您添加它们,后来删除它们,它不应引起任何问题。

走出放在这里肢体:如果您撰写的混入或使用抽象基类,它不打算被实例化,空__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__ 文档的其余部分(最新的3.7 dev文档)中找出更多的警告,我最近做出了很大的贡献。

对其他答案的批评

当前的最佳答案引用了过时的信息,而且非常容易波动,并且在某些重要方面未达到要求。

不要“仅__slots__在实例化许多对象时使用”

我引用:

__slots__如果要实例化大量(数百个,数千个)同一类的对象,则需要使用。”

例如,来自collections模块的抽象基类未实例化,但__slots__已为其声明。

为什么?

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

__slots__ 创建接口或混入时有助于重用。

的确,许多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__是节省对象空间。静态结构不允许创建后添加任何内容,而不是具有允许随时向对象添加属性的动态命令。这样可以为使用插槽的每个对象节省一个指令的开销

后半部分是一厢情愿的想法,并且超出了预期:

尽管有时这是一个有用的优化,但是如果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历史一致,来自Python 2.2中的Unifying类型和类。

如果您将内置类型作为子类,则多余的空间会自动添加到实例中以容纳__dict____weakrefs__。(__dict__尽管直到使用完,它才会被初始化,因此您不必担心空字典为您创建的每个实例所占用的空间。)如果不需要此多余的空间,则可以在短语中添加“ __slots__ = []”你的班。


14
哇,一个答案的地狱-谢谢!但是,我没有class Child(BaseA, BaseB): __slots__ = ('a', 'b')在empy-slot-parents那里找到这个例子。为什么在这里dictproxy创建而不是AttributeErrorc
Skandix '18

@Skandix感谢您引起我的打字错误,事实证明c不是实例化,当我将其保存到帖子历史记录中时,我可能忘记了编辑该部分。如果我做对了,使代码更具可复制性,它可能会早一点被抓住……再次感谢!
亚伦·霍尔

38
这个答案应该是有关的官方Python文档的一部分__slots__。认真!谢谢!
NightElfik

13
@NightElfik信不信由你,__slots__大约一年前,我为Python文档做出了贡献:github.com/python/cpython/pull/1819/files
Aaron Hall

极其详细的答案。我有一个问题:除非用法引起了警告,否则应该默认使用插槽吗?如果您知道要为速度/内存而苦苦挣扎,是否应该考虑使用插槽?换句话说,您应该鼓励新手学习它们并从一开始就使用它们吗?
freethebees

265

引用雅各布·哈伦Jacob Hallen)的话

正确的用法__slots__是节省对象空间。静态结构不允许创建后添加任何内容,而不是具有允许随时向对象添加属性的动态命令。[这种使用__slots__消除了每个对象一个字典的开销。]尽管这有时是有用的优化,但是如果Python解释器足够动态,以至仅在实际添加了dict时才需要该字典,则完全没有必要。宾语。

不幸的是,插槽有副作用。它们以一种可被控制怪胎和静态类型临时表滥用的方式更改具有插槽的对象的行为。这是不好的,因为控制怪胎应该滥用元类,而静态类型之间应该滥用装饰器,因为在Python中,应该只有一种明显的方法。

使CPython足够智能来处理节省的空间__slots__是一项艰巨的任务,这可能就是为什么它不在P3k更改列表中的原因(至今)。


86
我希望看到有关“静态类型” /修饰符的详细说明,没有贬义。引用缺席的第三方是无济于事的。__slots__不能解决与静态键入相同的问题。例如,在C ++中,不是限制成员变量的声明,而是将意外类型(和强制编译器)分配给该变量。我不宽容使用__slots__,只是对对话感兴趣。谢谢!
hiwaylon

126

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

不建议将其__slots__用于约束属性创建。

__slots__使用默认的(最旧的)泡菜协议将不能使用酸洗对象。有必要指定一个更高的版本。

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


10
我演示了在我的答案中腌制开槽的物体,并说明了答案的第一部分。
亚伦·霍尔

2
我明白您的意思,但是插槽也提供了更快的属性访问(如其他人所述)。在那种情况下,您无需“实例化许多(数百个,数千个)同一类的对象”即可获得性能。相反,您需要对同一实例的相同(带槽)属性进行大量访问。(如果我错了,请纠正我。)
Rotareti

61

每个python对象都有一个__dict__属性,该属性是包含所有其他属性的字典。例如,当您键入self.attrpython时实际上正在执行self.__dict__['attr']。您可以想象使用字典存储属性会花费一些额外的空间和时间来访问它。

但是,当您使用 __slots__,为该类创建的任何对象将没有__dict__属性。相反,所有属性访问都直接通过指针进行。

因此,如果要使用C样式结构而不是完整的类,则可以使用它__slots__来压缩对象的大小并减少属性访问时间。一个很好的例子是一个包含属性x&y的Point类。如果您有很多要点,可以尝试使用__slots__以节省一些内存。


10
不,一个类的实例__slots__定义是不是像C风格的结构。有一个将属性名称映射到索引的类级词典,否则以下操作将无法实现:class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)我真的认为这个答案应该得到澄清(如果需要,我可以这样做)。另外,我不确定这instance.__hidden_attributes[instance.__class__[attrname]]会比快instance.__dict__[attrname]
tzot 2011年

22

除了其他答案,这是使用的示例__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__,只需要多花一行(如果还没有,请将您的类变成新样式的类)。这样,您可以将这些类的内存占用量减少5倍,而在必要时以及必须编写自定义的pickle代码的代价是。


11

插槽对于库调用非常有用,以消除进行函数调用时的“命名方法分派”。SWIG 文档中提到了这一点。对于想要减少使用插槽的通常称为函数的函数开销的高性能库,速度要快得多。

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


7

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

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

instance(attribute)->值

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

属性(实例)->值

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

attribute(value)->实例


这是一个很好的分享,并且不能很好地评论也建议轻量化的答案之一,但它并不是对问题本身的完整答案。特别是(仅在问题的上下文中):为什么要有Flyweight,以及“应该避免什么情况……” __slots__
Merlyn Morgan-Graham 2014年

@Merlyn Morgan-Graham,它可以作为选择的提示:常规访问,__ slots__或flyweight。
德米特里·鲁巴诺维奇

3

一个非常简单的__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还dict为每个对象创建了一个属性(obj1obj2)。

假设我的课程Test是否有成千上万个此类对象?dict为每个对象创建一个附加属性将导致我的代码中大量开销(内存,计算能力等)。

解决方案: __slots__

现在,在下面的示例中,我的类Test包含__slots__属性。现在,我无法向对象添加新属性(attribute除外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'

2

另一个晦涩的用法__slots__是从ProxyTypes包(以前是PEAK项目的一部分)中向对象代理添加属性。它ObjectWrapper允许您代理另一个对象,但拦截与代理对象的所有交互。它不是很常用(并且没有Python 3支持),但是我们已经使用它来实现基于龙卷风的异步实现周围的线程安全的阻塞包装,该龙卷风使用线程安全来反弹通过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

1

您基本上没有用__slots__

在您认为自己可能需要的时间里__slots__,您实际上想使用轻量级轻量级设计模式。在这些情况下,您不再希望使用纯Python对象。相反,您希望在数组,结构或numpy数组周围使用类似Python对象的包装器。

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

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


17
Flyweight有什么用__slots__
oefe

3
@oefe:我当然没有收到您的问题。我可以引用我的回答,如果它有助于“当您认为您可能需要插槽时,您实际上要使用... Flyweight设计模式”。这就是Flyweight与slot的关系。您还有更具体的问题吗?
S.Lott

21
@oefe:Flyweight __slots__都是节省内存的优化技术。__slots__当您有许多对象以及Flyweight设计模式时,将显示出好处。两者解决相同的问题。
jfs

7
关于内存消耗和速度,在使用插槽和使用Flyweight之间是否存在可用的比较?
kontulai

8
尽管不管您相信与否,Flyweight在某些情况下肯定是有用的,但“在创建大量对象时如何减少Python中的内存使用量”的答案并不总是“不要将Python用于您的大量对象”。有时__slots__确实是答案,正如Evgeni指出的那样,可以将其添加为简单的事后想法(例如,您可以先关注正确性,然后再添加性能)。
Patrick Maupin 2015年

0

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

这是使用插槽和不使用插槽创建具有一百万个条目的对象树的比较。作为参考,在对树使用普通字典时的性能(在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):

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.