__slots__的用法?

Jeb*_*Jeb 697 python oop slots python-internals

__slots__Python中的目的是什么- 特别是关于我何时想要使用它,何时不想使用它?

Aar*_*all 890

在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
Run Code Online (Sandbox Code Playgroud)

Python doesn't object when you get this wrong (it probably should), problems might not otherwise manifest, but your objects will take up more space than they otherwise should.

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)
Run Code Online (Sandbox Code Playgroud)

The biggest caveat is for multiple inheritance - multiple "parent classes with nonempty slots" cannot be combined.

To accommodate this restriction, follow best practices: Factor out all but one or all parents' abstraction which their concrete class respectively and your new concrete class collectively will inherit from - giving the abstraction(s) empty slots (just like abstract base classes in the standard library).

See section on multiple inheritance below for an example.

Requirements:

  • To have attributes named in __slots__ to actually be stored in slots instead of a __dict__, a class must inherit from object.

  • 要防止创建a __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
Run Code Online (Sandbox Code Playgroud)

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Run Code Online (Sandbox Code Playgroud)

在Ubuntu上,Python 3.5的插槽访问速度提高了近30%.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
Run Code Online (Sandbox Code Playgroud)

在Windows上的Python 2中,我测得它的速度提高了约15%.

为什么使用__slots__:内存节省

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

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

The space saved over using __dict__ can be significant.

SQLAlchemy attributes a lot of memory savings to __slots__.

To verify this, using the Anaconda distribution of Python 2.7 on Ubuntu Linux, with guppy.hpy (aka heapy) and sys.getsizeof, the size of a class instance without __slots__ declared, and nothing else, is 64 bytes. That does not include the __dict__. Thank you Python for lazy evaluation again, the __dict__ is apparently not called into existence until it is referenced, but classes without data are usually useless. When called into existence, the __dict__ attribute is a minimum of 280 bytes additionally.

In contrast, a class instance with __slots__ declared to be () (no data) is only 16 bytes, and 56 total bytes with one item in slots, 64 with two.

For 64 bit Python, I illustrate the memory consumption in bytes in Python 2.7 and 3.6, for __slots__ and __dict__ (no slots defined) for each point where the dict grows in 3.6 (except for 0, 1, and 2 attributes):

       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
Run Code Online (Sandbox Code Playgroud)

So, in spite of smaller dicts in Python 3, we see how nicely __slots__ scale for instances to save us memory, and that is a major reason you would want to use __slots__.

Just for completeness of my notes, note that there is a one-time cost per slot in the class's namespace of 64 bytes in Python 2, and 72 bytes in Python 3, because slots use data descriptors like properties, called "members".

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
Run Code Online (Sandbox Code Playgroud)

Demonstration of __slots__:

To deny the creation of a __dict__, you must subclass object:

class Base(object): 
    __slots__ = ()
Run Code Online (Sandbox Code Playgroud)

now:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

Or subclass another class that defines __slots__

class Child(Base):
    __slots__ = ('a',)
Run Code Online (Sandbox Code Playgroud)

and now:

c = Child()
c.a = 'a'
Run Code Online (Sandbox Code Playgroud)

but:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

To allow __dict__ creation while subclassing slotted objects, just add '__dict__' to the __slots__ (note that slots are ordered, and you shouldn't repeat slots that are already in parent classes):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
Run Code Online (Sandbox Code Playgroud)

and

>>> swd.__dict__
{'c': 'c'}
Run Code Online (Sandbox Code Playgroud)

Or you don't even need to declare __slots__ in your subclass, and you will still use slots from the parents, but not restrict the creation of a __dict__:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
Run Code Online (Sandbox Code Playgroud)

And:

>>> ns.__dict__
{'b': 'b'}
Run Code Online (Sandbox Code Playgroud)

However, __slots__ may cause problems for multiple inheritance:

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

class BaseB(object): 
    __slots__ = ('b',)
Run Code Online (Sandbox Code Playgroud)

Because creating a child class from parents with both non-empty slots fails:

>>> 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
Run Code Online (Sandbox Code Playgroud)

If you run into this problem, You could just remove __slots__ from the parents, or if you have control of the parents, give them empty slots, or refactor to abstractions:

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!
Run Code Online (Sandbox Code Playgroud)

Add '__dict__' to __slots__ to get dynamic assignment:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'
Run Code Online (Sandbox Code Playgroud)

and now:

>>> foo = Foo()
>>> foo.boink = 'boink'
Run Code Online (Sandbox Code Playgroud)

So with '__dict__' in slots we lose some of the size benefits with the upside of having dynamic assignment and still having slots for the names we do expect.

When you inherit from an object that isn't slotted, you get the same sort of semantics when you use __slots__ - names that are in __slots__ point to slotted values, while any other values are put in the instance's __dict__.

Avoiding __slots__ because you want to be able to add attributes on the fly is actually not a good reason - just add "__dict__" to your __slots__ if this is required.

You can similarly add __weakref__ to __slots__ explicitly if you need that feature.

Set to empty tuple when subclassing a namedtuple:

The namedtuple builtin make immutable instances that are very lightweight (essentially, the size of tuples) but to get the benefits, you need to do it yourself if you subclass them:

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

usage:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
Run Code Online (Sandbox Code Playgroud)

And trying to assign an unexpected attribute raises an AttributeError because we have prevented the creation of __dict__:

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

You can allow __dict__ creation by leaving off __slots__ = (), but you can't use non-empty __slots__ with subtypes of tuple.

Biggest Caveat: Multiple inheritance

Even when non-empty slots are the same for multiple parents, they cannot be used together:

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
Run Code Online (Sandbox Code Playgroud)

Using an empty __slots__ in the parent seems to provide the most flexibility, allowing the child to choose to prevent or allow (by adding '__dict__' to get dynamic assignment, see section above) the creation of a __dict__:

class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Run Code Online (Sandbox Code Playgroud)

You don't have to have slots - so if you add them, and remove them later, it shouldn't cause any problems.

Going out on a limb here: If you're composing mixins or using abstract base classes, which aren't intended to be instantiated, an empty __slots__ in those parents seems to be the best way to go in terms of flexibility for subclassers.

To demonstrate, first, let's create a class with code we'd like to use under multiple inheritance

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)})'
Run Code Online (Sandbox Code Playgroud)

We could use the above directly by inheriting and declaring the expected slots:

class Foo(AbstractBase):
    __slots__ = 'a', 'b'
Run Code Online (Sandbox Code Playgroud)

But we don't care about that, that's trivial single inheritance, we need another class we might also inherit from, maybe with a noisy attribute:

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
Run Code Online (Sandbox Code Playgroud)

Now if both bases had nonempty slots, we couldn't do the below. (In fact, if we wanted, we could have given AbstractBase nonempty slots a and b, and left them out of the below declaration - leaving them in would be wrong):

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()
Run Code Online (Sandbox Code Playgroud)

And now we have functionality from both via multiple inheritance, and can still deny __dict__ and __weakref__ instantiation:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

Other cases to avoid slots:

  • Avoid them when you want to perform __class__ assignment with another class that doesn't have them (and you can't add them) unless the slot layouts are identical. (I am very interested in learning who is doing this and why.)
  • Avoid them if you want to subclass variable length builtins like long, tuple, or str, and you want to add attributes to them.
  • Avoid them if you insist on providing default values via class attributes for instance variables.

You may be able to tease out further caveats from the rest of the __slots__ documentation (the 3.7 dev docs are the most current), which I have made significant recent contributions to.

Critiques of other answers

The current top answers cite outdated information and are quite hand-wavy and miss the mark in some important ways.

Do not "only use __slots__ when instantiating lots of objects"

I quote:

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

Abstract Base Classes, for example, from the collections module, are not instantiated, yet __slots__ are declared for them.

Why?

If a user wishes to deny __dict__ or __weakref__ creation, those things must not be available in the parent classes.

__slots__ contributes to reusability when creating interfaces or mixins.

It is true that many Python users aren't writing for reusability, but when you are, having the option to deny unnecessary space usage is valuable.

__slots__ doesn't break pickling

When pickling a slotted object, you may find it complains with a misleading TypeError:

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

This is actually incorrect. This message comes from the oldest protocol, which is the default. You can select the latest protocol with the -1 argument. In Python 2.7 this would be 2 (which was introduced in 2.3), and in 3.6 it is 4.

>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
Run Code Online (Sandbox Code Playgroud)

in Python 2.7:

>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
Run Code Online (Sandbox Code Playgroud)

in Python 3.6

>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Run Code Online (Sandbox Code Playgroud)

So I would keep this in mind, as it is a solved problem.

Critique of the (until Oct 2, 2016) accepted answer

The first paragraph is half short explanation, half predictive. Here's the only part that actually answers the question

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

The second half is wishful thinking, and off the mark:

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.

Python actually does something similar to this, only creating the __dict__ when it is accessed, but creating lots of objects with no data is fairly ridiculous.

The second paragraph oversimplifies and misses actual reasons to avoid __slots__. The below is not a real reason to avoid slots (for actual reasons, see the rest of my answer above.):

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

It then goes on to discuss other ways of accomplishing that perverse goal with Python, not discussing anything to do with __slots__.

The third paragraph is more wishful thinking. Together it is mostly off-the-mark content that the answerer didn't even author and contributes to ammunition for critics of the site.

Memory usage evidence

Create some normal objects and slotted objects:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Run Code Online (Sandbox Code Playgroud)

Instantiate a million of them:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Run Code Online (Sandbox Code Playgroud)

Inspect with 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
...
Run Code Online (Sandbox Code Playgroud)

Access the regular objects and their __dict__ and inspect again:

>>> 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
...
Run Code Online (Sandbox Code Playgroud)

This is consistent with the history of Python, from Unifying types and classes in 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.

  • 这个答案应该是关于`__slots__`的官方Python文档的一部分.认真!谢谢! (27认同)
  • 哇,一个地狱的答案 - 谢谢!但是,我没有抓住`class Child(BaseA,BaseB):__slots__ =('a','b')`empy-slot-parents的例子.为什么这里创建了一个`dictproxy`而不是为`c`引发`AttributeError`? (10认同)
  • @NightElfik信不信由你,大约一年前我在`__slots__`上为Python文档做出了贡献:https://github.com/python/cpython/pull/1819/files (10认同)
  • 非常详细的答案。我有一个问题:是否应该使用插槽作为默认值_除非_使用遇到警告之一,或者如果您知道自己将在速度/内存方面遇到困难,那么插槽是否值得考虑?换句话说,您是否应该鼓励新手从一开始就了解它们并使用它们? (3认同)
  • @pepoluan 不,你不需要在 `__slots__` 中列出方法名称 - 但谢谢你的问题!槽声明在命名空间(`__dict__`)中创建一个描述符对象,就像方法定义一样。 (3认同)
  • 这个答案值得全场起立鼓掌。 (2认同)
  • @greatvovan 感谢您提醒我注意这一点,我已在两个位置更新了文本以明确这一点。如果这很好,或者您是否认为我错过了任何其他地方或任何其他问题,请告诉我。非常感激。 (2认同)
  • 使用 Python 3.10.2(通过 IPython 8.0.1)实际上,使用 `__slots__` 并没有提高性能:`%timeit get_set_delete_fn(not_slotted) -&gt; 每个循环 168 ns ± 0.367 ns(7 次运行的平均值 ± 标准差) ,每个循环 10,000,000 个)`,`%timeit get_set_delete_fn(slotted) -&gt; 每个循环 171 ns ± 0.106 ns(7 次运行的平均值 ± 标准差,每个循环 10,000,000 个)`。内存优势当然仍然存在。 (2认同)

Jef*_*uer 264

引用雅各布哈伦:

正确使用__slots__是为了节省物体的空间.除了拥有允许随时向对象添加属性的动态dict之外,还有一个静态结构,它不允许在创建后添加.[这种用法__slots__消除了每个对象的一个​​dict的开销.]虽然这有时是一个有用的优化,但如果Python解释器足够动态,那么它只需要dict时就完全没有必要了.宾语.

不幸的是,插槽有副作用.它们改变了具有插槽的对象的行为,这种方式可能被控制怪物和静态类型中断所滥用.这很糟糕,因为控制狂应该滥用元类,而静态类型weenies应该滥用装饰器,因为在Python中,应该只有一种明显的做法.

使CPython足够智能以处理节省空间而__slots__不是一项重大任务,这可能就是为什么它不在P3k(尚未)的更改列表中.

  • 我想看看有关"静态打字"/装饰点的一些细节,没有贬义.引用缺席的第三方是无益的.`__slots__`没有解决与静态类型相同的问题.例如,在C++中,不是成员变量的声明受到限制,而是为该变量赋予非预期类型(和强制执行的编译器).我不是在宽恕`__slots__`的使用,只是对谈话感兴趣.谢谢! (82认同)

Rya*_*yan 122

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

非常不鼓励使用__slots__约束属性创建,并且通常你想避免它,因为它打破了pickle,以及python的一些其他内省功能.

  • 我在答案中展示了一个开槽对象,并解决了答案的第一部分. (9认同)
  • 为什么它“非常沮丧”?我最近正在寻找一种限制动态属性创建的方法。我发现了一些东西,但没有提到插槽。现在我读到了有关老虎机的内容,它似乎正是我之前一直在寻找的东西。使用槽来防止在运行时添加属性有什么问题吗? (3认同)
  • 我明白你的意思,但插槽提供更快的属性访问(正如其他人所说).在这种情况下,您不需要*"实例化同一类"*的大量(数百,数千)对象以获得性能.您需要的是对同一实例的相同(时隙)属性的多次访问.(如果我错了,请纠正我.) (2认同)

Sur*_*raj 59

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

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

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

  • 不,定义了`__slots__`的类的实例是*不像C风格的结构.有一个类级别的字典映射属性名称到索引,否则以下是不可能的:`类A(对象):_ _ slots __ ="value",\n \na = A(); setattr(a,'value',1)`我真的认为应该澄清这个答案(如果你愿意,我可以这样做).另外,我不确定`instance .__ hidden_​​attributes [instance .__ class __ [attrname]]`比`instance .__ dict __ [attrname]`更快. (9认同)

Evg*_*eev 21

除了其他答案,这里有一个使用示例__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']
Run Code Online (Sandbox Code Playgroud)

因此,要实现__slots__它,它只需要一个额外的行(并使你的类成为一个新式的类,如果它还没有).这样,您可以将这些类的内存占用量减少5倍,代价是必须编写自定义pickle代码,如果有必要的话.


Dem*_*hun 11

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

现在这可能与OPs问题没有直接关系.它与构建扩展相关,而不是在对象上使用slot语法.但它确实有助于完成插槽使用的图片以及它们背后的一些推理.


Man*_*ngo 8

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

\n

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

\n
class Unslotted:\n    pass\ntest = Unslotted()\ntest.name = \'Fred\'\ntest.Name = \'Wilma\'\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
class Slotted:\n    __slots__ = (\'name\')\n    pass\ntest = Slotted()\ntest.name = \'Fred\'      #   OK\ntest.Name = \'Wilma\'     #   Error\n
Run Code Online (Sandbox Code Playgroud)\n

这次,第二个属性 ( Name) 被禁止,因为它\xe2\x80\x99 不在__slots__集合中。

\n

我建议尽可能使用 it\xe2\x80\x99s__slots__来更好地控制对象。

\n


Dmi*_*ich 7

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

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

实例(属性) - >值

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

attribute(instance) - > value

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

attribute(value) - > instance


N R*_*awa 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}
Run Code Online (Sandbox Code Playgroud)

如果你看上面的例子,你可以看到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'
Run Code Online (Sandbox Code Playgroud)


Sim*_*Art 7

从 Python 3.9 开始,adict可用于通过__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
"""
Run Code Online (Sandbox Code Playgroud)