Python - __slots__属性详解

作者: 严北 | 来源:发表于2018-09-04 08:54 被阅读29次

    简介

    __slots__允许我们声明并限定类成员,并拒绝类创建__dict____weakref__属性以节约内存空间

    Python是动态语言,对于普通的类,可以为类实例赋值任何属性,这些属性会存储在__dict__中:

    >>> class Student(object):
    ...     pass
    ...     
    >>> Abey = Student()
    >>> Abey.name = 'Abey'
    >>> Abey.__dict__
    {'name': 'Abey'}
    

    这样的特性带来两个问题:

    • 数据通过字典(Hash)存储所占用的空间较大
    • 如何禁止随意生成类属性

    当然,__slots__就能解决这两个问题。通过__slots__属性限定类属性的创建:

    >>> class Student(object):
    ...     __slots__ = ('name', 'age')
    ...     
    >>> Abey = Student()
    >>> Abey.name = 'Abey'
    >>> Abey.gender = 'Female'
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'gender'
    >>> Abey.__dict__
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__dict__'
    

    可以看到,在定义了__slots__变量后,Student类实例已经不能随意创建不在__slots__定义内的属性gender,同时实例中也不再有__dict__结构。

    用法

    继承树

    __slots__在继承中有两种表现:

    • 子类未声明__slots__时,不继承父类的__slots__,即此时子类实例可以随意赋值属性
    • 子类声明__slots__时,继承父类的__slots__,即此时子类的__slots__为其自身+父类的__slots__

    以下面的父类为例:

    >>> class Student(object):
    ...     __slots__ = ('name', 'age')
    ...     
    

    创建一个子类不声明__slots__,该类实例可以创建父类__slots__限定之外的属性gender

    >>> class SubStudent(Student):
    ...     pass
    ... 
    >>> Bob = SubStudent()
    >>> Bob.gender = 'Male'
    >>> Bob.__dict__
    {'gender': 'Male'}
    

    而创建一个声明__slots__的子类,该类属性则只能创建父类__slots__+自身__slots__限定的属性:

    >>> class SubStudent2(Student):
    ...     __slots__ = 'gender'
    ...     
    >>> Cathy = SubStudent2()
    >>> Cathy.gender = 'Female'
    >>> Cathy.name = 'Cathy'
    >>> Cathy.teacher = 'Mrs. Wang'
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'SubStudent2' object has no attribute 'teacher'
    

    注意:子类的__slots__本身已经继承自父类,无需重复声明父类已声明的属性。例如上例,重复声明会多占用内存空间:

    >>> class SubStudent3(Student):
    ...     __slots__ = ('name', 'age', 'gender')
    ...    
    >>> from sys import getsizeof
    >>> getsizeof(Student()), getsizeof(SubStudent2()), getsizeof(SubStudent3())
    (56, 64, 80)
    

    性能对比

    我们为什么要使用__slots__呢?

    更快速地赋值属性

    参考Stack Overflow回答中给出的数据:

    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)下,slots为Python3.5带来了接近30%的赋值速度提升:

    >>> 0.3664822799983085 / 0.2846834529991611
    1.2873325658284342
    

    节约内存空间

    由于不使用__dict__存储对象的属性,__slots__在一些场景下能够节约极大的内存空间。具体数据可以查看参考中的回答链接,不赘述。

    参考

    [1] Usage of __slots__?, Aaron Hall, Stack Overflow

    推荐阅读

    [1] Data model, Python Document
    [2] python __slots__ 使你的代码更加节省内存, david_bj, 51CTO
    [3] 使用__slots__, 廖雪峰

    相关文章

      网友评论

        本文标题:Python - __slots__属性详解

        本文链接:https://www.haomeiwen.com/subject/fygwwftx.html