03 面向对象之封装

一、封装

1.1 概念                                                         

什么是:

    1.当有一些数据不希望外界可以直接修改时,当有一些函数不希望给外界使用时,将不需要对外提供的内容都隐藏起来

         2. 对外提供公共方法对其访问

优点:

    1.保障关键数据的安全;

    2. 将变化隔离,对外部隐藏实现细节,隔离复杂度,便于使用;(如将pc的启动项、加载项、检测项等封装到open下) 

    3. 提高复用性; 

特点:

    1.外界不能直接访问

    2.内部依然可以使用

权限控制

      在python只要两种权限,

      1.公开的   默认就是公开的

      2.私有的  只能由当前类自己使用

 

1.2 封装语法                                             

封装有两种:

      封装属性,私有化属性  属性前加杠杠

      封装方法,私有化方法  方法前加杠杠

1.2.1  封装属性        

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形后的结果

2.在外部是无法通过__x这个名字访问到的,只能通过接口访问

3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的

4.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

 

class Student:
    def __init__(self,name,age):
        self.__name = name #封装属性 :杠杠
        self.agehhh = age  #点后面随便写,写什么调用时对应就行了
    def get_name(self): #外界访问的接口
        print(t.__name)
t = Student(444,555)
print(t.agehhh) #555
# print(t.name)#报错 外部不能访问封装的
# print(t.__name)  #也报错
print(t._Student__name) #444  杠+类名+杠杠+name  这种是封装的本质 能调出来但是没意思啊
t.get_name() #444  通过接口访问,正规合法路经

1.2.2  封装方法

在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

#正常情况
>>> 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

二、外部访问私有的内容

属性虽然被封装了,但是外界还是需要使用的,通过定义方法来完成外界对私有属性的修改和访问

这是一个下载器类,需要提供一个缓存大小这样的属性
缓存大小不能超过内存限制,不嗯能够让用户随便设置,因此我们对缓存进行封装,
但是有些用户电脑实在太水,需要比一般人更小的缓存,必须修改,我们可以定义方法也就是接口,给用户修改的机会

class Downloader:
    def __init__(self,filename,url,buffer_size):  #文件名 链接 缓冲大小
        self.filename = filename
        self.url = url
        self.__buffer_size= buffer_size

    def start_download(self):
        if self.__buffer_size <= 1024*1024:  #内部随便访问私有属性
            print("开始下载....")
            print("当前缓冲器大小",self.__buffer_size)
        else:
            print("内存炸了! ")


    def set_buffer_size(self,size):  #给用户提供修改接口,并且在这里还可以进行某些额外限制,比不封装让用户直接改好多了
        #可以在方法中添加额外的逻辑
        if not type(size) == int:
            print("大哥 缓冲区大小必须是整型")
        else:
            print("缓冲区大小修改成功!")
            self.__buffer_size = size

    def get_buffer_size(self):
        return self.__buffer_size

d = Downloader("葫芦娃","http://www.baicu.com",1024*1024)

# 通过函数修改内部封装的属性
d.set_buffer_size(1024*512)

# 通过函数访问内部封装的属性
print(d.get_buffer_size())
print(d.filename)
d.start_download()

三、property  setter deleter  装饰器

通过方法来修改或访问属性,本身没什么问题,但是这给对象的使用者带来了麻烦.

使用必须知道哪些是普通属性,哪些是私有属性,需要使用不同的方式来调用他们

1.@property   该装器用在获取属性的方法上 
2.@key.setter  该装器用在修改属性的方法上  
3.@key.deleter 该装器用在删除属性的方法上 

注意:key是被property装饰的方法的名称 也就是属性的名称 
内部会创建一个对象 变量名称就是函数名称  
所以在使用setter和deleter时 必须保证使用对象的名称去调用方法 
所以是 key.setter

 

 

 

class A:
    def __init__(self,name,key):
        self.__name = name
        self.__key = key

    @property
    def key(self):
        print("111")
        return self.__key

    @key.setter
    def key(self,new_key):
        print("222")
        if new_key <= 100:
            self.__key = new_key
        else:
            print("key 必须小于等于100")

    @key.deleter
    def key(self):
        print("333")
        print("不允许删除该属性")
        del self.__key

a = A("jack", 123)
print(a.key)
a.key = 21
del a.key
三种装饰器

 

 #   property属性 (可以使用property来进行计算属性)

 

 property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值(不装饰返回的是一个属性,一大串字符)

将一个类的函数定义成特性以后,去调用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

#计算圆的周长和面积
import math
class Circle:
    def __init__(self,radius): #圆的半径radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #计算面积

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #计算周长

c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
class Square:
    def __init__(self,width):
        self.width = width
        # self.area = self.width * self.width  这样写在实例化对象时就定死了边长为10
    @property
    def area(self):
        return self.width * self.width

s = Square(10)
print(s.area)  #100
s.width = 20
print(s.area)  #400
动态计算正方形面积

 

四、接口   抽象类  鸭子类型

1.  接口                                                      

接口是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码

接口本质是一套协议标准,遵循这个标准的对象就能被调用 

接口目的就是为了提高扩展性(如USB接口,不管谁来了直接插上就好了)

例如电脑提前指定制定一套USB接口协议,只要你遵循该协议,你的设备就可以被电脑使用,不需要关心到底是鼠标还是键盘

案例:

#协议标准
class
USB: #制定的USB协议,也就是其他类必须写成这种格式才符合规则 def open(self): pass def close(self): pass def read(self): pass def write(self): pass class Mouse(USB): #来了一个鼠标,继承USB父类 def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") def read(self): print("获取了光标位置....") def write(self): print("鼠标不支持写入....") class KeyBoard(USB): #又来了一个键盘,继承USB def open(self): print("键盘开机.....") def close(self): print("键盘关机了...") def read(self): print("获取了按键字符....") def write(self): print("可以写入灯光颜色....")
#接口
def pc(usb_device): #这是一个接口,只要把鼠标或者键盘等符合USB规则的类插进来立马运行 usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() pc(m) # 将鼠标传给电脑 k = KeyBoard() pc(k) # 来了一个键盘对象

在上述案例中,PC接口的代码一旦完成,后期无论什么样的设备 只要遵循了USB接口协议,都能够被电脑所调用

接口主要是方便了对象的使用者,降低使用者的 学习难度,只要学习一套使用方法,就可以以不变应万变,比如你会用鼠标了键盘也就知道怎么用了

 

问题来了:

如果子类没有按照你的协议来设计,也没办法限制他,将导致代码无法运行,那怎么办呢?


class
Mouse(): #不遵循USB协议还想使用pc接口,不报错才怪呢 def star(self): print("鼠标开机.....") def end(self): print("鼠标关机了...") def read_obj(self): print("获取了光标位置....") def write_obj(self): print("鼠标不支持写入....")

鼠标不遵循USB协议,自己搞了个协议,你往pc接口上插,一下就报错了,怎么办?

为了防止子类乱定义标准,USB协议中使用抽象类把子类的模式限制死了,必须一毛一样的模式 


2. 抽象类 abc模块(abstract class)
(了解即可 没什么用)

抽象类指的是包含抽象方法(没有函数体的方法)的类,

 

 

作用:可以限制子类必须类中定义的抽象方法(上面的USB父类可以用此方法装饰,如果U盘不按套路来实例化的时候就会报错失败,不给插PC接口的机会)

import abc #抽象类要引入abc模块
class AClass(metaclass=abc.ABCMeta): #固定书写模式
    @abc.abstractmethod  #装饰哪个,子类中就必须有什么,少一个都不行,并且子类中每个方法下面必须有函数体,不能只写pass
    def run(self):
        pass  @abc.abstractmethod
def run1(self):
        pass

class B(AClass):
    def run(self):
        print("runrunrurn...")  #方法体内必须有执行代码,如果只写pass,实例化时也会报错
    def run1(self):
        print("runrunrurn...")
b = B()  #如果只定义run 一个方法,不定义run1,会报错,父类中有几个,子类中就必须有几个方法,并且得一样

3.  鸭子类型                                              

上面引用abc模块的抽象化确实可以限制子类的方法形式,但是python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该自觉遵守相关协议

所以有了鸭子类型这么一说:

如果这个对象长得像鸭子,走路像鸭子,那就他是鸭子

你只要保证你的类按照相关的协议类编写,也可以达到提高扩展性的目的

class Mouse: #我们不再定义USB协议,这里也不用再继承USB,只要自己乖乖的按照规则来写,pc接口就能识别
    def open(self):
        print("鼠标开机.....")
    def close(self):
        print("鼠标关机了...")
    def read(self):
        print("获取了光标位置....")
    def write(self):
        print("鼠标不支持写入....")

class UDisk:
    def open(self): #这一次我不胡来了,我按规矩来,pc接口就能识别了
        print("U盘启动了...")
    def close(self):
        print("U盘关闭了...")
    def read(self):
        print("读出数据")
    def write(self):
        print("写入数据")

def pc(usb_device):
    usb_device.open()
    usb_device.read()
    usb_device.write()
    usb_device.close()
    
m = Mouse()
pc(m)
u = UDisk()
pc(u)

 

 

 

 

posted @ 2019-07-27 18:47  www.pu  Views(248)  Comments(0Edit  收藏  举报