面向对象——封装

    xiaoxiao2022-07-02  98

    封装

    层次一:类就是麻袋,这本身就是一种封装

               从字面意义上去理解封装,装就是搬家的时候,把书、电脑、杯子什么的都往袋子里装,封就是把这个袋子封起来,封起来之后,从外面就什么都看不到了,就是所谓的‘隐藏’。在面向对象里面,这个袋子就是类或者对象,下面举个例子:

    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__,这里就不做讲解

        小结:

         封装看起来简单,但不要去为了封装而封装,要提前识别哪些数据要使用,那些数据需要隐藏,不能滥用双下划线,否则的话,项目做到后期,你的类里面会出现很多接口函数,这也就是麻袋上面的洞,那么你的类看起来也是烂七八糟,后面会持续补充其他封装的内容,封装不只是这么简单。

    如果你对编程感兴趣或者想往编程方向发展,可以关注微信公众号【筑梦编程】,大家一起交流讨论!小编也会每天定时更新既有趣又有用的编程知识!

    最新回复(0)