封装
层次一:类就是麻袋,这本身就是一种封装
从字面意义上去理解封装,装就是搬家的时候,把书、电脑、杯子什么的都往袋子里装,封就是把这个袋子封起来,封起来之后,从外面就什么都看不到了,就是所谓的‘隐藏’。在面向对象里面,这个袋子就是类或者对象,下面举个例子:
class People: star = 'earth' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id)
定义好这个类之后,其他人要调用你的类,就是这样的:
from (文件名) import People p1 = People('2131415151','pengfy',18) p1.get_id() >>> 我是私有方法,我的id是2131415151
我可以使用你这个类,但是并不知道你具体是怎么实现的,这就属于一种封装,但封装并不只是隐藏。
层次二:类中定义私有的,只有在类的内部才能使用,外部无法使用
首先,什么是内部,什么是外部?在类里面用就是内部,调用这个类再使用,就是外部喽。那怎么定义私有呢?说这个之前,就要说要Python的两个约定:
1.单下划线开头
类里面单下划线开头的属性,一般都是定义的私有,继续看上面的例子:
# -*- coding: utf-8 -*- class People: star = 'earth' _star = 'earth1' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id) p1 = People('2131415151','pengfy',18) print(p1.star) print(p1._star)
运行一下,结果是:
>>>earth
>>>earth1
说好的私有呢?怎么都能打印,但是,请记住,这只是一个约定,你非要调用,还是可以的,哈哈。
2.双下划线开头
# -*- coding: utf-8 -*- class People: star = 'earth' _star = 'earth1' __star = 'earth2' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id) p1 = People('2131415151','pengfy',18) print(p1.star) print(p1._star) print(p1.__star)
看一下运行结果:
>>> earth >>> earth1 AttributeError: 'People' object has no attribute '__star'
报错了,好像可以啊,双下划线外部不能访问嘛,你可以试试内部能不能访问,肯定是可以的,这就完了私有属性啦?有点早,不信你试试把p1.__star替换成p1._People__star,运行结果如下:
class People: star = 'earth' _star = 'earth1' __star = 'earth2' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id) p1 = People('2131415151','pengfy',18) print(p1.star) print(p1._star) print(p1._People__star) >>>earth >>>earth1 >>>earth2
写成这样就出来啦?为什么要这么写?自己找答案!打开类的属性字典找找就好了:
print(People.__dict__) >>>{'__module__': '__main__', 'star': 'earth', '_star': 'earth1', '_People__star': 'earth2', '__init__': <function People.__init__ at 0x00000206A3BF26A8>, 'get_id': <function People.get_id at 0x00000206A3BF2730>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
找到没有,Python中类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__star,会变形为_People__star,这种变形需要注意的是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._C__D,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形,看下边例子就知道了
class People: star = 'earth' _star = 'earth1' __star = 'earth2' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id) p1 = People('2131415151','pengfy',18) print(p1.__dict__) p1.__star = 'earth666' print(p1.__dict__) >>>{'id': '2131415151', 'name': 'pengfy', 'age': 18} >>>{'id': '2131415151', 'name': 'pengfy', 'age': 18, '__star': 'earth666'}
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的,如下:
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from A
层次三:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个借口供外部使用(这才是真正意义的封装,不要为了封装而封装)
上面已经知道了,双下划线开头,外部无法直接访问,但是内部可以,这样的话,你可以定义一个函数直接return出去,就OK了:
class People: star = 'earth' _star = 'earth1' __star = 'earth2' def __init__(self,id,name,age): self.id = id self.name = name self.age = age def get_id(self): print('我是私有方法,我的id是%s'%self.id) def tell_star(self): return self.__star p1 = People('2131415151','pengfy',18) print(p1.tell_star()) >>> earth2
这样做外部要拿这个属性时,也可以拿到,tell_star这个函数就是这个类的接口函数,当然上面的例子讲的都是数据属性,函数属性呢?肯定也是一样可以:
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
打印一下结果:
插卡
用户认证
输入取款金额
打印账单
取款
了解:
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,这里就不做讲解
小结:
封装看起来简单,但不要去为了封装而封装,要提前识别哪些数据要使用,那些数据需要隐藏,不能滥用双下划线,否则的话,项目做到后期,你的类里面会出现很多接口函数,这也就是麻袋上面的洞,那么你的类看起来也是烂七八糟,后面会持续补充其他封装的内容,封装不只是这么简单。