32.__slots__类属性

 

 

 

 

__slots__类属性

 

1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
 
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
 
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。
实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。
使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。
大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。

 

 

1、字典位于实例的'心脏'。__dict__属性跟踪所有实例属性
2、字典会占据大量内存,如果你有一个属性数量很少的类,但有很多实例,那么正好是这种情况。为内存上的考虑,用户现在可以使用__slots__属性来替代__dict__。
3、基本上,__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性的集合来表示。它可以是一个列表,元组或可迭代对象。

也可以是标识实例能拥有的唯一的属性的简单字符串。任何试图创建一个其名不在__slots__中的名字的实例属性都将导致 AttributeError 异常:

 

#!/usr/bin/env python
#coding:utf-8


class SlottedClass(object):
    __slots__ = ('foo', 'bar')

c = SlottedClass()
c.foo = 42
c.xxx = "don't think so"

 

执行结果:

c.xxx = "don't think so"
AttributeError: 'SlottedClass' object has no attribute 'xxx'

 

 

4、这种特性的主要目的是节约内存。其副作用是某种类型的"安全",它能防止用户随心所欲的动态增加实例属性。带__slots__属性的类定义不会存在__dict__了(除非你在__slots__中增加'__dict__'元素)

 

说明

__slots__是用来限制实例的属性的,__slots__可以规定实例是否应该有__dict__属性;__slots__不能限制类的属性。

 

只有__slots__列表内的这些变量名可赋值为实例属性

 

例子1:

class A:
    __slots__=['name']
    def __init__(self):
        self.name='js'
        self.age=22
a=A()

 

执行结果:

<__main__.A instance at 0x05991CD8>

__slots__属性对普通类没什么作用

 

例子2:

#!/usr/bin/env python
#coding:utf-8

class A(object):
    __slots__=['name']
    def __init__(self):
        self.name = 'js'
        self.age= 22
a=A()

 

执行结果:

 self.age= 22
AttributeError: 'A' object has no attribute 'age'

 

__slots__只是限制实例,对类对象没有影响

 

#!/usr/bin/env python
#coding:utf-8

class A(object):
    __slots__=['name','city']
    age=22
    def __init__(self):
        self.name='js'
a=A()
print('A __slots__: ', A.__slots__)
print('a __slots__: ', a.__slots__)
print('A __dict__: ', A.__dict__)
print('a __dict__: ', a.__dict__)

 

执行结果:

#当我们试图调用a.__dict__时,出现错误,因为该属性没有出现在__slots__中,所以禁止赋值或者访问。
Traceback (most recent call last): File "G:/PycharmProject/fullstack2/week1/star.py", line 13, in <module> print('a __dict__: ', a.__dict__) AttributeError: 'A' object has no attribute '__dict__'

#事实上,所有定义在__slots__中的属性都会放置在类的__dict__当中,即使没有使用的属性(city)也是如此。
#而当实例需要取对象时,总是会先到类的__dict__中进行检查,如果类的__dict__中的属性是一个对象且该对象对属性的读取做了一些限制,
#那么就会直接影响到实例是否能够调用该属性。__slots__的工作原理是如此,后面介绍的描述符类亦是如此。

('A __slots__: ', ['name', 'city']) ('a __slots__: ', ['name', 'city'])

#在类的__dict__中,也会存入__slots__属性。
('A __dict__: ', dict_proxy({'city': <member 'city' of 'A' objects>, '__module__': '__main__', 'name': <member 'name' of 'A' objects>, 'age': 22, '__slots__': ['name', 'city'], '__doc__': None, '__init__': <function __init__ at 0x04EB8E70>}))

 

可以同时存在__slots__和__dict__吗?

 

可以,如果把__dict__属性存入__slots__中,那么就允许使用__dict__属性了。
这时,如果所有__slots__中定义的属性存在__slots__中,如果没有定义的属性,那么存在__dict__中,从而实现属性的分别管理。
dir函数获取所有定义在__slots____dict__中的属性。或者通过list(getattr(X, 'dict', [])) + getattr(X, 'slots', [])来得到所有的属性。

#!/usr/bin/env python
#coding:utf-8

class A(object):
    __slots__=('name','city','__dict__')
    def __init__(self):
        self.name='js'
        self.age=22
a=A()
print('A __slots__: ', A.__slots__)
print('a __slots__: ', a.__slots__)
print('A __dict__: ', A.__dict__)
print('a __dict__: ', a.__dict__)

 

执行结果:

('A __slots__: ', ('name', 'city', '__dict__'))
('a __slots__: ', ('name', 'city', '__dict__'))
#连__dict__都会保存在类的__dict__中,且属性值是一个object。
('A __dict__: ', dict_proxy({'city': <member 'city' of 'A' objects>, 

'__module__': '__main__', 'name': <member 'name' of 'A' objects>,

'__slots__': ('name', 'city', '__dict__'), '__dict__': <attribute '__dict__' of 'A' objects>,

'__doc__': None, '__init__': <function __init__ at 0x05988E70>})) ('a __dict__: ', {'age': 22})

 

如果子类中没有__slots__,但是超类中有...

 

 

class Super(object):
    __slots__=['name']
    pass
class Sub(Super):
    def __init__(self):
        self.name='js'
        self.age=22
a=Sub()
print('Sub __slots__: ', Sub.__slots__)
print('a __slots__: ', a.__slots__)
print('Sub __dict__: ', Sub.__dict__)
print('a __dict__: ', a.__dict__)

 

执行结果:

#顺利继承到了Super的__slots__属性
('Sub __slots__: ', ['name']) ('a __slots__: ', ['name'])
#此时Python用了大量的黑暗魔法,这时我们看到Sub的__dict__中居然出现了__dict__属性,且值为特殊的对象,
相当于Sub.__slots__=Super.__slots__+['__dict__'],从而实现如果在__slots__中出现的属性存在__slots__中,没有出现的存在Sub的实例的__dict__中。
('Sub __dict__: ', dict_proxy({'__dict__': <attribute '__dict__' of 'Sub' objects>, '__module__': '__main__', 

'__weakref__': <attribute '__weakref__' of 'Sub' objects>, '__doc__': None,

'__init__': <function __init__ at 0x05798EF0>})) ('a __dict__: ', {'age': 22})
#我们确实看到了age存在了子类的实例的__dict__中。

 

如果子类和父类都有__slots__...

 

#!/usr/bin/env python
#coding:utf-8

class Super(object):
    __slots__=['name','age']
class Sub(Super):
    __slots__=['city']
print('Sub __slots__: ', Sub.__slots__)
print('Sub __dict__: ', Sub.__dict__)

 

执行结果:

#父类中的__slots__没有对子类产生影响
('Sub __slots__: ', ['city'])
#再次证明了上面的说法,如果一定需要父类的__slots__进行叠加,那么需要手动设置为__slots__=Super.__slots__ + ['city'],
所以可以看出Python通过了大量的黑暗魔法,从而达到__slots__不具有常规的继承特性。
('Sub __dict__: ', dict_proxy({'city': <member 'city' of 'Sub' objects>, '__module__': '__main__', 
'__slots__': ['city'], '__doc__': None}))

 

如果一个子类继承自一个没有__slots__的超类...

 

如果一个子类继承自一个没有__slots__的超类,那么超类的__dict__属性总是可以访问的,使得子类中的一个__slots__无意义。
留给你自己验证一下吧。

 

总结

 

1、__slots__用来设计成对实例的__dict__的限制,只有__dict__出现在__slots__中,实例才会有__dict__属性。
否则,只有出现在__slots__中的属性才可以被使用。 2、Python特意设计成__slots__没有常规的继承特性,所以只有超类具有__slots__且其__dict__属性没有出现在其中,这时子类的__slots__才有意义,
且子类的__slots__不继承父类的__slots__。

 

用Python中的__slots__缓存资源以节省内存开销的方法

 

python老鸟都应该看过那篇非常有吸引力的 Saving 9 GB of RAM with Python’s __slots__ 文章,作者使用了__slots__让内存占用从25.5GB降到了16.2GB。在当时来说,这相当于用一个非常简单的方式就降低了30%的内存使用,着实惊人。作者并没有提到他的业务特点和代码,那我们就基于《fluent python》中的例子来验证下是不是有这么厉害:

 

#!/usr/bin/env python
#coding:utf-8

from __future__ import print_function
import resource


class A(object):
    def __init__(self):
        self.a = 'string'
        self.b = 10
        self.c = True


class B(object):
    __slots__ = ['a', 'b', 'c']
    def __init__(self):
        self.a = 'string'
        self.b = 10
        self.c = True


def test(cls):
    mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    l = []
    for i in range(500000):
        l.append(cls())
    mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    del l
    print('Class: {}:\n'.format(getattr(cls, '__name__')))
    print('Initial RAM usage: {:14,}'.format(mem_init))
    print('  Final RAM usage: {:14,}'.format(mem_final))
    print('-' * 20)


if __name__ == '__main__':
    import sys
    test(globals()[sys.argv[1].upper()])
    
    
    

 

执行结果:

[root@host-192-168-3-6 xitong]# python test.py a
Class: A:

Initial RAM usage:          4,796
  Final RAM usage:        197,356
--------------------
[root@host-192-168-3-6 xitong]# python test.py b
Class: B:

Initial RAM usage:          4,792
  Final RAM usage:         60,920
--------------------

 

2种方法初始内存略有差别,但是由于这个差别和总内存量相比太小而忽略不计,结论就是:

使用slots可以让内存使用减少3.5倍!!# 通过 (200 - 4) / ((60 - 4) * 1.0) 计算得来

 

那么用slot就是非非常那个有必要吗?事实上500000个实例这种机会非常少见,用不用完全根据业务来决定,并不要以偏概全。因为(敲黑板了哈)使用__slots__也是有副作用的:

1. 每个继承的子类都要重新定义一遍__slots__
2. 实例只能包含哪些在__slots__定义的属性,这对写程序的灵活性有影响,比如你由于某个原因给instance设置一个新的属性,
比如instance.a = 1, 但是由于a不在__slots__里面就直接报错了,你得不断地去修改__slots__或者用其他方法迂回的解决 3. 实例不能有弱引用(weakref)目标,否则要记得把__weakref__放进__slots__

 

第三点有点难理解,我写个例子看看吧:

例子1:

#!/usr/bin/env python
#coding:utf-8

from weakref import ref

class A(object):
    __slots__ = ['b']
    def __init__(self):
        self.b = 1


class B(object):
    __slots__ = ['b', '__weakref__']
    def __init__(self):
        self.b = 1

a = A()
r = ref(a)

 

 

执行结果:

Traceback (most recent call last):
  File "G:/PycharmProject/fullstack2/week1/star.py", line 18, in <module>
    r = ref(a)
TypeError: cannot create weak reference to 'A' object

 

例子2:

 

#!/usr/bin/env python
#coding:utf-8

from weakref import ref

class A(object):
    __slots__ = ['b']
    def __init__(self):
        self.b = 1


class B(object):
    __slots__ = ['b', '__weakref__']
    def __init__(self):
        self.b = 1


b = B()
r = ref(b)
print r

 

执行结果:

<weakref at 05B302D0; to 'B' at 05B2BB70>

所以实例不超过万级别的类,__slots__是不太值得使用的。

 

slots属性参考:

https://www.cnblogs.com/jessonluo/p/4752512.html

http://www.jb51.net/article/118024.htm

posted @ 2019-06-19 08:50  钟桂耀  阅读(198)  评论(0编辑  收藏  举报