让人误会的私有属性和__slots__——禁止访问类的实例属性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_42681866/article/details/83185266

写在前面

  刚认识python的__slots__属性时,就对其可以禁止访问类的实例属性这一功能感到由衷喜欢,总比私有属性的不那么私有来的可爱(只要想访问,python的私有属性都是可以访问)。如果你对这两个知识点还不是很熟悉,那这片浅显易懂的文章就值得一看。

__私有属性

  提到python的私有属性,即是在属性前加双下划线,最大的作用就是防止别人访问该私有属性,如果访问就会报错:AttributeError: module object has no attribute ‘__x’,举例如下:在这里插入图片描述
  为什么说python的私有属性让人误会呢?在讲这个知识点前,先介绍下python是如何存储实例属性(定义在__init__下的属性)。当我们定义实例属性时,python会把其存入__dict__中,实际上就是个字典,以上图为例,当我们访问y属性时,实际上访问的是a.__dict __[‘y’],具体如下图所示:
在这里插入图片描述
  细心的朋友肯定发现在__dict__里还有_A__x,也就是说通过a.__dict __[’_A__x’]就能访问到我们不想让人访问的私有属性__x,而__dict__默认是省略的,所以直接a._A__x就可以访问私有属性了,所以才说python的私有属性并不那么私有,阻挡不了非开发人员的硬性访问。
在这里插入图片描述

_私有属性–名义上的保护属性

  除了不那么私有的__私有属性,python还有程序员约定俗成的保护属性:_属性, 因为相对私有属性的双下划线显得更好看点,加上一个下划线表示程序编写人员不希望使用人员访问该属性,但是可以正常访问,如下所示。

 class B:
...     def __init__(self, x, y):
...             self._x = x
...             self.y = y

在这里插入图片描述
  因此该私有属性除了口头约定,却没有真正的实质作用。但身为python程序员,如果见到别人定义了_x,__y这类的属性,得严格遵守规定,不要访问这些“私有属性”。另外要注意的是,当一个模块里定义了_x,__y的私有属性,通过from module import *是无法导入这些私有属性的,然而依旧可以使用from module import _provatefunc将其导入。

__slots __的强大作用

  让我们说回python类的另一强大属性__slots__,凡是涉及到双下划线的,就是python的特殊属性/方法,也称为魔法属性/方法(magic attr/method)。
  为什么说__slots__让人误会呢,之前有了解到该属性的朋友第一反应肯定是:这属性我知道啊,就是禁止访问类的实例属性。这话说的没错,相对私有属性的不那么私有,我们可以通过__slots__来严格防止别人访问定义的实例属性。但是大家所不知道的是,通过__slots__来禁止别人访问实例属性的方法其实是__slots__的副作用,其开发者开发该属性的初心是用来加速对实例的访问速度,以及节省大量的内存空间
  通过前面的实例,我们知道了python在存储实例的过程实际上是存储到__dict__字典里,每次调用也是从该字典里调用。从字典里调用的好处就是底层是散列表,所以访问速度很快,即为O(1)的访问速度,但却会造成大量的内存浪费,说到底就是用空间换时间的底层结构(不了解的朋友推介阅读我之前写的这篇:python字典的底层介绍)。
  __slots__的作用就是一旦在类里定义了该属性,那么python将再不会创建__dict__了,而是把实例属性存储到__slots__里面(实际上是定义了一个描述器来存储),如下图所示。
在这里插入图片描述

  看到这里,我们知道定义了__slots__属性后,python会将实例属性存储到该结构里面,因为__dict__是采用空间换时间的方法,所以换成该存储方式肯定能节约大量的空间。同时还能加快运行速度,这是因为未定义__slots__时,每次存储和访问实例属性,都要经过a.__dict __['x]这么一系列功夫,简单拆分就是1.a.__dict __ 2.取值;但是__slots__的存储和访问直接一步到位,所以时间上要更快。这边定义代码来显示其运行时长对比:

class Test: #定义正常类
    pass

class Test_slots: #定义带__slots__的类
    __slots__ = 'x'

import time
t1 = time.perf_counter() #利用time的perf_counter函数来计算时长,该函数相对time更精准
t = Test()
for i in range(10**8):  #在10**7的次数内,存储x的值
    t.x = 10
elapsed1 = time.perf_counter()-t1
t2 = time.perf_counter()
t = Test_slots()
for j in range(10**8):
    t.x = 10
elapsed2 = time.perf_counter()-t2

print('__dict__运行时长:%s' % elapsed1)
print('__slots__运行时长:%s' %elapsed2)

结果如下,定义了__slots__的类存储时间快了大概1.4s。

__dict__运行时长:8.237609633999998
__slots__运行时长:6.886858459999999

当次数从10的8次方变成9次方时,结果如下,快了接近6s。

__dict__运行时长:79.209338574
__slots__运行时长:73.41103178299998

看完了时间的性能优化,我们看下空间的性能优化:

class Test:
    def __init__(self, x, y):
        self.x = x
        self.y = y
class Test_slots:
    __slots__ = 'x','y'
    def __init__(self, x, y):
        self.x = x
        self.y = y
import resource  #用于计算内存的模块
mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
L1 = [Test(3, 4) for i in range(10**7)] #创建10**7个实例
mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

mem_init__slots = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
L2 = [Test_slots(3, 4) for j in range(10**7)]
mem_final__slots = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

print('__dict__运行初始内存:%s' % mem_init)
print('__dict__运行结束内存:%s' % mem_final)
print('__dict__运行占用内存:%s' % (mem_final-mem_init))
print('__slots__运行初始内存:%s' % mem_init__slots)
print('__slots__运行结束内存:%s' % mem_final__slots)
print('__slots__运行占用内存:%s' % (mem_final__slots-mem_init__slots))

结果如下,可以看到__slots__的占用内存为660MB,而__dict__的占用内存却高达1.7G,这个差距还是非常明显的。

__dict__运行初始内存:6516736
__dict__运行结束内存:1722048512
__dict__运行占用内存:1715531776
__slots__运行初始内存:1722048512
__slots__运行结束内存:2382458880
__slots__运行占用内存:660410368

注意事项

  slots对性能的影响还是非常巨大的,但其有几个注意事项:
1.每个子类都要定义__slots__属性,因为解释器会忽略继承的__slots__属性
2.实例只能拥有__slots__定义的属性,这个是__slots__的副作用,如果你非要把这个当成功能去使用也行,另外可以在__slots__在定义__dict__属性,这样就可以不再限制实例属性,相对的也就丧失了节省内存的作用。
3.作为了解,一旦定义了__slots__的话,就不能使用弱引用引用实例了,除非在__slots__里面定义__weakref__。

总结

  本次博客就讲到这里,相对__slots__这一魔法属性,python还有许多的定制类属性,也非常值得学习;另外还有@property装饰器的强大功能,这些后续逐一介绍。我们下回再见。

猜你喜欢

转载自blog.csdn.net/weixin_42681866/article/details/83185266