RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1206730
Accepted
Danis
Danis
Asked:2021-11-19 14:35:34 +0000 UTC2021-11-19 14:35:34 +0000 UTC 2021-11-19 14:35:34 +0000 UTC

Python 中 __slots__ 的目的是什么?

  • 772

__slots__Python的目的是什么。特别是关于何时使用它,何时不使用它?

问题的翻译__slots__ 的用法?通过@Jeb

python
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Danis
    2021-11-19T14:35:34Z2021-11-19T14:35:34Z

    __slots__Python的目的是什么。特别是关于何时使用它,何时不使用它?

    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' # избыточные foo и bar
    

    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'
    

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

    要解决此限制,请遵循以下指南:提取除一个或所有父抽象之外的所有抽象,它们的具体类将从其继承,以及您的新类 - 为抽象提供空槽(就像标准库中的抽象基类一样)

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

    要求:

    1. 为了将名为 in 的属性__slots__存储在插槽中而不是存储在 中__dict__,该类必须继承自object.

    2. 为了防止创建__dict__,你必须从objectw 继承,并且继承中的所有类都必须声明__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 中 dict 在 3.6 中递增的每个点的内存消耗(以字节为单位__slots__)__dict__(属性 0、1 和 2 除外):

               Python 2.7             Python 3.6
        attrs  __slots__  __dict__*   __slots__  __dict__* | *(слоты не определены)
        none   16         56 + 272†   16         56 + 112† | †если __dict__ упоминается
        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
    

    因此,尽管__dict__在 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__,您必须子类化对象:

    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() # Нет ошибок
    

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

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

    现在

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

    因此,使用'__dict__'in slot 时,我们失去了一些大小优势,因为我们拥有动态分配的好处,并且仍然有我们期望的名称的插槽。

    当你从一个没有槽的对象继承时,你在使用它时会得到相同的语义__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' # увы, будет работать, если пусто, т.е. ()
    
    >>> 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 
    

    在父级上使用 empty__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或使用不打算实例化的抽象基类__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), в которую я недавно внес значительный вклад.

    перевод ответа от участника @AaronHall

    • 10

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5