面向对象——封装

封装

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

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

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

    小结:

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

原文地址:https://www.cnblogs.com/pengfy/p/10610193.html