关于python: __slots__的使用?

Usage of __slots__?

在python中,__slots__的目的是什么?尤其是在我想何时使用它,何时不使用它时?


In Python, what is the purpose of __slots__ and what are the cases one should avoid this?

最后暴雪来句:

《特殊的属性允许你__slots__没有这两个国家审你期望从你的对象有两个instances,与预期的结果:

好。

  • 阿姨属性的访问。
  • 储蓄的记忆体空间。
  • 空间的冰从储蓄

    好。

  • 储存价值的证明人的而不是__dict__槽。
  • denying __dict__鸭类__weakref__创造如果父母拒绝他们,你__slots__DECLARE。
  • 快速caveats

    小的忠告,你应该只读DECLARE城堡一小时(特别是在一个树的传承。例如:

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    class Base:
        __slots__ = 'foo', 'bar'

    class Right(Base):
        __slots__ = 'baz',

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

    Python对象不让你得到这个错误的信息可能会这样),否则没有问题,但你的目标宣言,将更多的空间比内地的IP,否则,他们应该。

    好。

    1
    2
    3
    >>> from sys import getsizeof
    >>> getsizeof(Right()), getsizeof(Wrong())
    (64, 80)

    《biggest多传承"冰"的父母多类与非空槽"不能被联合。

    好。

    两个accommodate这个限制,遵循最佳实践的因素:选择一个或所有的一切但他们父母的abstraction级分别为混凝土,混凝土等级和你的新的采矿inherit将从现在的abstraction(S)的空槽(就像抽象类的标准库的图书馆)。

    好。

    看下面的传承型在线多个实例。

    好。 要求:

    • "有两个属性实际上是在__slots__可存储在两个插槽,而不是__dict__A,A类必须从objectinherit。

      好。

    • 两个目标是创造一__dict__,你必须从inherit object和所有类的DECLARE和传承必须__slots__没有他们可以拥有一'__dict__'录入。

      好。

    有很多的细节,如果你希望继续阅读。

    好。 为什么使用__slots__阿姨:属性的访问。

    《典范(Guido van Rossum Python,事实上,他创造了美国__slots__阿姨对属性的访问。

    好。

    它是两个重要的琐碎的demonstrate measurably阿姨访问:

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

    好。

    1
    2
    3
    4
    >>> min(timeit.repeat(get_set_delete_fn(slotted)))
    0.2846834529991611
    >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
    0.3664822799983085

    《冰缝隙存取几乎30%的阿姨在Python 3.5在Ubuntu。

    好。

    1
    2
    >>> 0.3664822799983085 / 0.2846834529991611
    1.2873325658284342

    在Python中的Windows 2在线测量的信息约有15%的禁食。

    好。 为什么使用__slots__:记忆的储蓄

    另一个目的是__slots__减少存储空间的需要,每个对象实例的IP。

    好。

    我自己的贡献,美国的文件明确,这背后的原因:

    好。

    The space saved over using __dict__ can be significant.

    Ok.

    SQLAlchemy的属性很多记忆两个__slots__储蓄。

    好。

    这两个验证,采用康达学院分布在Python 2.7 Ubuntu Linux操作系统,与guppy.hpy(又名heapy sys.getsizeof)和《大审院一级__slots__没有宣布什么,鸭子,冰的64个字节。这不包括《__dict__。谢谢你对Python的懒惰的评价的话,__dict__显然不想变成冰的存在直到它被引用的类,但没有数据通常是无用的。当叫到__dict__属性的存在,是一个最小的additionally 280个字节。

    好。

    在一审级的对比与__slots__宣布两个()(没有数据)的冰,只有16个字节,字节和56总与一项在64和双槽。

    好。

    Python的64位的存储器,在illustrate消费的字节在Python 2.7和3.6,__slots__(NO和__dict__插槽定义为每个点在快译通生长在3.6(除了为0,1,2和属性):

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
           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

    所以,在大学spite小词典在Python 3,我们看到如何nicely __slots__量表instances拯救我们的记忆,这就是冰的一个主要原因你会想使用__slots__

    好。

    我只是完全性票据,票据,有一个一小时的成本每槽类的命名空间(64字节的Python 72字节2和3在Python中使用数据槽,因为descriptors状特性,称为"成员"。

    好。

    1
    2
    3
    4
    5
    6
    >>> Foo.foo
    <member 'foo' of 'Foo' objects>
    >>> type(Foo.foo)
    <class 'member_descriptor'>
    >>> getsizeof(Foo.foo)
    72

    大学__slots__演示:

    两个拒绝"创造一__dict__,你必须subclass object

    好。

    1
    2
    class Base(object):
        __slots__ = ()

    现在:

    好。

    1
    2
    3
    4
    5
    6
    >>> 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'

    这是另一个类或subclass __slots__

    好。

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

    现在:鸭

    好。

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

    但:

    好。

    1
    2
    3
    4
    5
    >>> 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__中(请注意,时隙是有序的,不应重复父类中已有的时隙):好的。

    1
    2
    3
    4
    5
    6
    7
    class SlottedWithDict(Child):
        __slots__ = ('__dict__', 'b')

    swd = SlottedWithDict()
    swd.a = 'a'
    swd.b = 'b'
    swd.c = 'c'

    和好的。

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

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

    1
    2
    3
    4
    class NoSlots(Child): pass
    ns = NoSlots()
    ns.a = 'a'
    ns.b = 'b'

    还有:好的。

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

    但是,__slots__可能导致多重继承的问题:好的。

    1
    2
    3
    4
    5
    class BaseA(object):
        __slots__ = ('a',)

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

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

    1
    2
    3
    4
    5
    6
    >>> 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__,或者如果您可以控制父代,则为其提供空槽,或者重构为抽象:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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__中,得到动态分配:

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

    现在:好的。

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

    因此,在槽中使用EDOCX1[1]时,我们会失去一些尺寸优势,因为动态分配的好处是,我们仍然可以为我们期望的名称提供槽。好的。

    当您从一个没有时隙的对象继承时,当您使用__slots__中的名称(在__slots__中)指向时隙值时,您会得到相同的语义,而任何其他值都放在实例的__dict__中。好的。

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

    如果需要该功能,也可以将__weakref__显式添加到__slots__。好的。在对NamedDuple进行子类化时设置为空元组:

    名为duple的builtin使不可变的实例非常轻(本质上是tuples的大小),但要获得好处,如果您将它们子类化,则需要自己执行:好的。

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

    用途:好的。

    1
    2
    3
    4
    5
    >>> nt = MyNT('bar', 'baz')
    >>> nt.bar
    'bar'
    >>> nt.baz
    'baz'

    并且试图分配一个意外的属性会引发一个AttributeError,因为我们已经阻止了__dict__的创建:好的。

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

    您可以通过不使用__slots__ = ()来允许__dict__创建,但不能使用具有元组子类型的非空__slots__。好的。最大的警告:多重继承

    即使多个父项的非空插槽相同,也不能一起使用:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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__:好的。

    1
    2
    3
    4
    5
    class Foo(object): __slots__ = ()
    class Bar(object): __slots__ = ()
    class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
    b = Baz()
    b.foo, b.bar = 'foo', 'bar'

    你不需要有插槽-所以如果你添加它们,然后再删除它们,就不会造成任何问题。好的。

    这里有一个缺陷:如果您正在编写mixin或使用抽象的基类,而这些类并不打算被实例化,那么在这些父类中使用空的__slots__似乎是子类灵活性的最好方法。好的。

    为了演示,首先,让我们用在多重继承下要使用的代码创建一个类。好的。

    1
    2
    3
    4
    5
    6
    7
    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)})'

    我们可以通过继承和声明预期的槽来直接使用上面的内容:好的。

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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,并将它们从下面的声明中删除-将它们留在其中是错误的):好的。

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

    现在我们可以通过多重继承从这两方面获得功能,并且仍然可以拒绝__dict____weakref__的实例化:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> 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__"

    我引述:好的。

    "You would want to use __slots__ if you are going to instantiate a lot (hundreds, thousands) of objects of the same class."

    Ok.

    例如,来自collections模块的抽象基类没有实例化,但是为它们声明了__slots__。好的。

    为什么?好的。

    如果用户想要拒绝__dict____weakref__创建,那么这些东西在父类中不能使用。好的。

    __slots__有助于在创建接口或混合时的可重用性。好的。

    确实,许多Python用户并不是为了可重用性而写的,但是当您这样做时,拥有拒绝不必要空间使用的选项是很有价值的。好的。__slots__不破坏酸洗

    当酸洗有槽物体时,您可能会发现其投诉带有误导性的TypeError:好的。

    1
    2
    >>> 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。好的。

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

    在Python 2.7中:好的。

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

    在Python 3.6中好的。

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

    所以我会记住这一点,因为这是一个已解决的问题。好的。对(截至2016年10月2日)接受答案的批评

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

    The proper use of __slots__ is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. This saves the overhead of one dict for every object that uses slots

    Ok.

    下半部分是一厢情愿的想法,而且毫无意义:好的。

    While this is sometimes a useful optimization, it would be completely unnecessary if the Python interpreter was dynamic enough so that it would only require the dict when there actually were additions to the object.

    Ok.

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

    第二段过于简单化,遗漏了避免__slots__的实际原因。下面不是避免吃角子老虎机的真正原因(出于实际原因,请参阅上面的其余答案):好的。

    They change the behavior of the objects that have slots in a way that can be abused by control freaks and static typing weenies.

    Ok.

    然后,它将继续讨论用python实现这一反常目标的其他方法,而不是与__slots__讨论任何事情。好的。

    第三段更是一厢情愿。总之,这主要是离谱的内容,回答者甚至没有作者和贡献弹药的批评者的网站。好的。内存使用证据

    创建一些普通对象和槽对象:好的。

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

    举例说明一百万个:好的。

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

    guppy.hpy().heap()检查:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 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__并再次检查:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> 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 2.2中统一类型和类的历史是一致的。好的。

    If you subclass a built-in type, extra space is automatically added to the instances to accomodate __dict__ and __weakrefs__. (The __dict__ is not initialized until you use it though, so you shouldn't worry about the space occupied by an empty dictionary for each instance you create.) If you don't need this extra space, you can add the phrase"__slots__ = []" to your class.

    Ok.

    好啊。


    引用雅各布·哈伦的话:

    The proper use of __slots__ is to save space in objects. Instead of having
    a dynamic dict that allows adding attributes to objects at anytime,
    there is a static structure which does not allow additions after creation.
    [This use of __slots__ eliminates the overhead of one dict for every object.] While this is sometimes a useful optimization, it would be completely
    unnecessary if the Python interpreter was dynamic enough so that it would
    only require the dict when there actually were additions to the object.

    Unfortunately there is a side effect to slots. They change the behavior of
    the objects that have slots in a way that can be abused by control freaks
    and static typing weenies. This is bad, because the control freaks should
    be abusing the metaclasses and the static typing weenies should be abusing
    decorators, since in Python, there should be only one obvious way of doing something.

    Making CPython smart enough to handle saving space without __slots__ is a major
    undertaking, which is probably why it is not on the list of changes for P3k (yet).


    如果要实例化同一类的许多(几十万)对象,您将希望使用__slots____slots__只作为内存优化工具存在。

    强烈建议不要使用__slots__来约束属性创建,一般来说,您希望避免使用它,因为它会破坏pickle以及python的一些其他自省功能。


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

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

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


    除其他答案外,这里还有一个使用__slots__的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    >>> 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代码。


    时隙对于库调用非常有用,可以在进行函数调用时消除"命名方法调度"。这在swig文档中提到。对于希望减少函数开销的高性能库来说,使用槽的通常调用函数要快得多。

    现在这可能与作战问题没有直接关系。与在对象上使用slots语法相比,它更多地与构建扩展相关。但它确实有助于完成插槽的使用以及背后的一些推理。


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

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

    实例(属性)->值

    在_uu slots_uuu access中,属性的名称充当字典,实例充当字典中查找值的键。

    属性(实例)->值

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

    属性(值)->实例


    __slots__的另一个有点模糊的用法是从proxytypes包(以前是peak项目的一部分)向对象代理添加属性。它的ObjectWrapper允许您代理另一个对象,但拦截与被代理对象的所有交互。它不是很常用(也不支持python 3),但是我们使用它来实现基于Tornado的异步实现周围的线程安全阻塞包装器,它通过IOLoop反弹对代理对象的所有访问,使用线程安全的concurrent.Future对象来同步和返回结果。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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

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

    问题:没有__slots__

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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}

    如果您查看上面的示例,可以看到obj1和obj2有自己的x和y属性,而python也为每个对象(obj1和obj2)创建了dict属性。

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

    解决方案:使用EDOCX1[1]

    在下面的示例中,我的类测试包含__slots__属性。现在我不能向我的对象添加新的属性(除了属性x),而且python不再创建dict属性。这就消除了每个对象的开销,如果有许多对象,这些开销会变得很重要。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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'

    你基本上没有使用__slots__

    在您认为可能需要__slots__的时候,实际上您希望使用轻量级或轻量级的设计模式。当您不再希望使用纯Python对象时,这些情况会出现。相反,您需要一个python对象,比如数组、结构或numpy数组的包装器。

    1
    2
    3
    4
    5
    6
    7
    class Flyweight(object):

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

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

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


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ********** 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):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)

    测试代码,详细模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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