__slots__ 的用法?

2024-11-27 10:43:00
admin
原创
11
摘要:问题描述:在 Python 中它的用途是什么__slots__——特别是关于我什么时候想使用它,什么时候不想使用它?解决方案 1:在 Python 中,这样做的目的是什么__slots__?在什么情况下应该避免这样做?总结:特殊属性__slots__允许您明确说明期望对象实例具有哪些实例属性以及预期结果:更快...

问题描述:

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


解决方案 1:

在 Python 中,这样做的目的是什么__slots__?在什么情况下应该避免这样做?

总结:

特殊属性__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表示,他实际上__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__是减少每个对象实例占用的内存空间。

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

使用时节省的空间__dict__非常大。

SQLAlchemy 将大量内存节省归功于__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 中字典增长的每个点(未定义槽)的内存消耗__slots____dict__以字节为单位)(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 中的字典较小,但我们看到__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__,您必须子类化objectobject在 Python 3 中所有内容都是子类,但在 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'

可以__dict__通过离开来允许创建__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 开发文档是最新的)中找出进一步的注意事项,我最近对其做出了重大贡献。

对其他答案的批评

当前的最佳答案引用了过时的信息,并且相当不切实际,在一些重要方面没有切中要点。

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

我引用一下:

__slots__“如果您要实例化同一类的大量(数百、数千)对象,那么您会想要使用它。”

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

为什么?

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

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

确实,许多 Python 用户编写的代码并不具有可重用性,但是当您具有可重用性时,拥有拒绝不必要的空间使用的选项是很有价值的。

__slots__不会破坏腌制

当对一个有槽的物体进行 pickle 操作时,你可能会发现它会产生误导性的抱怨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 解释器足够动态,那么只有在对象实际添加内容时才需要字典,那么这将是完全没有必要的。

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 中统一类型和类开始

如果您将内置类型子类化,则会自动向实例添加额外空间以容纳__dict____weakrefs__。(__dict__不过, 直到您使用时才会初始化,因此您不必担心每个创建实例的空字典所占用的空间。)如果您不需要这些额外空间,则可以将短语“ __slots__ = []”添加到您的类中。

解决方案 2:

引用Jacob Hallen 的话:

的正确用法__slots__是为了节省对象空间。与允许随时向对象添加属性的动态字典不同,它有一个静态结构,不允许在创建后添加属性。[这种用法__slots__消除了为每个对象添加一个字典的开销。]虽然这有时是一种有用的优化,但如果 Python 解释器足够动态,以至于只有在实际向对象添加属性时才需要字典,那么这种优化就完全没有必要了。

不幸的是,插槽有一个副作用。它们会改变具有插槽的对象的行为,而控制狂和静态类型狂可能会滥用这种行为。这很糟糕,因为控制狂应该滥用元类,而静态类型狂应该滥用装饰器,因为在 Python 中,应该只有一种显而易见的方法来完成某件事。

让 CPython 足够智能,能够处理节省空间的问题__slots__是一项重大任务,这可能就是为什么它还没有列入 P3k 的变更列表中(目前还没有)。

解决方案 3:

__slots__如果您要实例化大量(数百、数千)同一类的对象,那么您会想要使用它。__slots__它仅作为内存优化工具存在。

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

使用默认(最旧的)pickle 协议无法对对象进行 pickle__slots__操作;因此需要指定更高版本。

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

解决方案 4:

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

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

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

解决方案 5:

除了其他答案之外,这里还有一个使用示例__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 代码(如果有必要)。

解决方案 6:

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

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

解决方案 7:

一个非常简单的属性示例__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有它们自己的xydict属性,并且 python 还为每个对象(obj1obj2)创建了一个属性。

假设我的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'

解决方案 8:

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

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

实例(属性)-->值

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

属性(实例)-->值

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

属性(值) --> 实例

解决方案 9:

除了其他答案之外,__slots__还通过将属性限制为预定义列表来增加一点印刷安全性。这一直是 JavaScript 的一个问题,它还允许您向现有对象添加新属性,无论您是否愿意。

这是一个普通的无槽对象,它不执行任何操作,但允许您添加属性:

class Unslotted:
    pass
test = Unslotted()
test.name = 'Fred'
test.Name = 'Wilma'

由于 Python 区分大小写,因此拼写相同但大小写不同的两个属性是不同的。如果您怀疑其中一个是输入错误,那么运气不好。

使用插槽,您可以限制这一点:

class Slotted:
    __slots__ = ('name')
    pass
test = Slotted()
test.name = 'Fred'      #   OK
test.Name = 'Wilma'     #   Error

这次,第二个属性(Name)不被允许,因为它不在__slots__集合中。

我建议最好尽可能使用__slots__以更好地控制对象。

解决方案 10:

从 Python 3.9 开始,dict可以通过 为属性添加描述__slots__None可以用于没有描述的属性,即使给出了描述,私有变量也不会出现。

class Person:

    __slots__ = {
        "birthday":
            "A datetime.date object representing the person's birthday.",
        "name":
            "The first and last name.",
        "public_variable":
            None,
        "_private_variable":
            "Description",
    }


help(Person)
"""
Help on class Person in module __main__:

class Person(builtins.object)
 |  Data descriptors defined here:
 |
 |  birthday
 |      A datetime.date object representing the person's birthday.
 |
 |  name
 |      The first and last name.
 |
 |  public_variable
"""

解决方案 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

解决方案 12:

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

以下是使用插槽和不使用插槽创建具有一百万个条目的对象树的比较。作为参考,还提供了使用普通字典创建树的性能(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、appar 除了插槽):

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

解决方案 13:

除了本文中其他答案中描述的无数优点之外 - 对于注重内存的紧凑实例,比更易变__dict__的实例更不容易出错等等- 我发现使用__slots__提供了更清晰的类声明,因为类的实例变量明确地公开了。

为了解决__slots__声明中的继承问题,我使用了这个元类:

import abc

class Slotted(abc.ABCMeta):
    
    """ A metaclass that ensures its classes, and all subclasses,
        will be slotted types.
    """
    
    def __new__(metacls, name, bases, attributes, **kwargs):
        """ Override for `abc.ABCMeta.__new__(…)` setting up a
            derived slotted class.
        """
        if '__slots__' not in attributes:
            attributes['__slots__'] = tuple()
        
        return super(Slotted, metacls).__new__(metacls, name, # type: ignore
                                                        bases,
                                                        attributes,
                                                      **kwargs)

...如果在继承塔中将其声明为基类的元类,则确保从该基类派生的所有内容都将正确继承__slots__属性,即使中间类未声明任何属性。如下所示:

# note no __slots__ declaration necessary with the metaclass:
class Base(metaclass=Slotted):
    pass

# class is properly slotted, no __dict__:
class Derived(Base):
    __slots__ = 'slot', 'another_slot'

# class is also properly slotted:
class FurtherDerived(Derived):
    pass

解决方案 14:

本质上来说,你对 毫无用处__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

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

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   642  
  引言在当今快速变化的科技市场中,企业要想保持竞争力,就必须具备高效的产品开发流程。小米作为一家以创新驱动的科技公司,其集成产品开发(IPD)流程在业界颇受关注。其中,技术路线图规划作为IPD流程的核心环节,对于确保产品技术领先、满足市场需求以及实现长期战略目标至关重要。本文将深入探讨小米IPD流程中的技术路线图规划,分...
华为IPD是什么   0  
  在当今快速变化的商业环境中,项目管理的高效执行是企业成功的关键。为了应对日益复杂的产品开发挑战,企业纷纷寻求将产品开发流程(Product Development Process, PDCP)与集成产品开发(Integrated Product Development, IPD)流程相结合的策略,以实现更高效、更协同的...
IPD管理   0  
  在当今竞争激烈的市场环境中,提高客户满意度是企业持续发展和成功的关键。为了实现这一目标,企业需要不断优化其产品开发和管理流程。IPD(Integrated Product Development,集成产品开发)流程图作为一种高效的项目管理工具,能够帮助企业实现跨部门协作、优化资源配置,并最终提升客户满意度。本文将深入探...
IPD流程是谁发明的   0  
  在项目管理领域,集成产品开发(IPD, Integrated Product Development)流程被视为提升项目成功率的关键框架。IPD通过其系统化的方法,将产品开发过程中的各个阶段紧密连接,确保从概念到市场的每一步都经过深思熟虑和高效执行。本文将深入探讨IPD流程的六个核心阶段如何深刻影响项目成功,并为项目管...
IPD流程中CDCP   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用