QQ登录

只需一步,快速开始

开启左侧

Python学习第五十八天—类和对象7-私有变量和__slots__

[复制链接]
15271953841 发表于 2024-3-22 08:48:23 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
私有变量和__slots__
一、“私有变量”:就是指通过某种手段,使得对象中的属性或方法无法被外部所访问。
是一种保护机制,保护我们自己,编程语言通过层层限制其实就是阻止程序员干傻事,犯错误的。就是这么一个逻辑。Python则相反,百分之百相信程序员,给予程序员最大的自由度,但程序员要为自己编写的代码负责任。
所以python中,那种仅限成为一个对象内部才能访问的私有变量并不存在,所以我们这里把“私有变量”加上了引号,但是Python也并不是啥都不干,它引入了一种叫name mangling的机制,翻译过来叫名字改编,名称改写或名称修饰,语法也很简单,就是在名字前面加上两个连续的下横线。
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
class C:
    def __init__(self,x):
        self.__x = x            (__x是私有变量)
    def set_x(self,x):
        self.__x = x
    def get_x(self):
        print(self.__x)

        
c = C(250)
c.__x                       (报错原因:此时我们无法直接通过私有变量名来访问到这个变量的,既然叫私有,那就是尽可能的避免你从外部去访问,想要访问到变量的值,必须通过指定的接口,比如这段代码的set_x和get_x的方法。
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    c.__x
AttributeError: 'C' object has no attribute '__x'
c.get_x()                    (获取它的值)
250
c.set_x(520)                 (设置它的值,把250改成520)
c.get_x()                    (获取它的值)
520
c.__dict__
{'_C__x': 520}
c._C__x                     (名字改编术:下划线加类名,再加上变量(私有变量__x)的名字,所以在Python中,所谓私有变量,就是你想要私有的变量,偷偷地改了一个名字,而且改法是有规律的,方法也是同样的道理)
520
class D:
    def __func(self):                 (__func想把func方法隐藏起来,在它的名字前加两个下划线)
        print("Hello FishC.")

        
d = D()
d.__func()              (报错,提示无法访问)
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    d.__func()
AttributeError: 'D' object has no attribute '__func'. Did you mean: '_D__func'?
d._D__func()              (破解方案,但是不希望这样做,用了双下划线是不希望外界访问到的,所以我们要遵循这个约定俗成的规则,老老实实写代码,安安分分做程序。)
Hello FishC.
那还有一个问题:如果我们在对象诞生后,是否能够通过动态添加属性的方法,来添加一个私有变量呢?可以
但是后期添加的__y并不会被名字改编。结论:
名字改编是发生在类实例化对象时候的事情。
以后开发中还会看到:_单个下划线开头的变量,或者单个下划线结尾的变量_。_单个下划线开头的变量是仅供内部使用的变量,属于约定俗成的命名规则,所以看到这样的名字,不要随意修改它或访问它。单个下划线结尾的变量_:比如class是Python来定义类的,不能随便使用把这个名字用在其它地方,如果你非要使用一个变量名叫class,可以在class后面加一个下横线。
c.__y=250
c.__dict__
{'_C__x': 520, '__y': 250}

二、效率提升之道,佛说:舍/得之道,人生之道也。
有时舍一得十,有时候为了得一而要舍十,后者显然是不划算的。
Python为了对象的灵活性,有时候就会牺牲大量的存储空间,比如说动态添加属性这个技能,拿到一个对象,随便就可以给它添加属性,但是你知道它背后实现的原理是什么吗?是字典。就是我们非常熟悉的__dict__属性,对象的属性通常都是存放在这个__dict__里面的。
class C:
    def __init__(self,x):
        self.x = x

        
c = C(250)
c.x
250
c.__dict__                 (这个dict就是存放对象c这个属性的位置)
{'x': 250}
c.y=520                    (动态添加属性)
c.y
520
c.__dict__
{'x': 250, 'y': 520}
c.__dict__["z"] = 666             (直接通过给字典添加键值对的方式创建对象的属性。不存在的键z,赋值666,那么它就会自动创建)
c.__dict__
{'x': 250, 'y': 520, 'z': 666}
c.z
666
字典和集合高效背后的玄机:拿空间来换的时间。
使用字典比较费内存外,其它都是优点。
三、如果我们明确知道一个类的对象设计出来,就只是需要那么固定的几个属性,并且将来也不会有动态添加属性这种功能的需求,那么利用字典来存放属性,这种空间上的牺牲,就是蠢蠢的浪费。针对这种情况,Python专门设计了一个叫做__slots__的类属性,避免了利用字典来存放造成空间上的浪费。
class C:
    __slots__=["x","y"]              (赋值一个列表给它,列表中的元素是你希望这个对象可以使用的属性名称,比如说,我就希望这个类C实例化的对象只能x,y两个属性。)
    def __init__(self,x):
        self.x = x

        
c=C(250)                     
c.x
250
c.y=520
c.y
520
c.z = 666                 (报错原因:如果我们在类的内部想要创建一个slots不包括的属性也是不被允许的。)
Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    c.z = 666
AttributeError: 'C' object has no attribute 'z'
class D:
    __slots__=["x","y"]
    def __init__(self,x,y,z):
        self.x = x
        self.y = y
        self.z = z

d=D(3,4,5)                      (报错)
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    d=D(3,4,5)
  File "<pyshell#59>", line 6, in __init__
    self.z = z
AttributeError: 'D' object has no attribute 'z'
报错原因:因为使用了__slots__属性,对象就会划分一个固定大小的空间,来存放指定的属性,这个时候__dict__属性就不需要了,空间因此也被节约出来,外国一网友测试上面的程序,改成加入__slots__属性,限定对象空间来存放指定属性,一下子节省9G空间。
但使用了__slots__属性的副作用也是明显的,那就是要以牺牲Python动态语言的灵活性作为前提,使用__slots__属性就没有办法拥有动态添加属性的功能。
四、继承自父类的__slots__属性是不会在子类中生效的。
Python只会关注各个具体的类中定义的__slots__属性。
class E(C):
    pass

e = E(250)
e.x                (继承自C,所以C能做的,E也能做)
250
e.y=250
e.y                (继承自C,所以C能做的,E也能做)
250
e.z=666             (动态的添加一个z,添加成功了。新添加的”z”=666,就放到e的__dict__属性里面去了)
e.z
666
e.__slots__             (继承自C,所以E有这个__slots__,虽E有__slots__属性,但是这个__slots__是类C的,这个是继承下来的。)
['x', 'y']
e.__dict__              (同时也会拥有一个__dict__属性,新添加的”z”=666,就放到e的__dict__属性里面去了)
{'z': 666}

再次强调一下:继承自父类的__slots__属性,是不会在子类中生效的,
Python只关注各个具体类中定义的__slots__属性。
客服热线
400-1234-888 周一至周日:09:00 - 21:00
公司地址:襄阳市樊城区长虹路现代城5号楼188

创客帮MAKER.BAND青少年创客创意社区是一个融教育、科技、体育资讯为一体的综合服务平台,专注于教育创新、专注于科技体育、专注于教育资讯。

Powered by Discuz! X3.4 © 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表