25、多态,封装

昨天说了面向对象三大特性的继承,今天来说多态和封装,很多其他程序员说python不支持多态没有多态,并不是python没有多态,而是python处处皆多态。今天的讲课重点会放在封装。

 

本篇导航:

 

一、多态

1、多态

多态指的是一类事物有多种形态

水有多种形态:冰 水雾 水

动物有多种形态:人,狗,猪

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')
多态

文件有多种形态:文本文件,可执行文件

import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形态之一:文本文件
    def click(self):
        print('open file')

class ExeFile(File): #文件的形态之二:可执行文件
    def click(self):
        print('execute file')
文件

2、多态性

多态性是指在不考虑实例类型的情况下使用实例

在面向对象方法中一般是这样表述多态性:

向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()
多态性

3、鸭子类型

Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

例子

 

二、封装

封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:

1、 将变化隔离; 

2.、便于使用;

3.、提高复用性; 

4.、提高安全性;

封装原则:

1、 将不需要对外提供的内容都隐藏起来;

2、 把属性都隐藏,提供公共方法对其访问。

1、私有变量

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__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是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
私有变量

特点:

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

2、这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

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

注意:

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

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): #在定义时就变形为_B__fa
         print('from B')
 
b=B()
b.test()
#from A
私有方法

3、扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length


#使用者
r1=Room('卧室','egon',20,20,20)
r1.tell_area() #使用者调用接口tell_area


#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high


#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
 r1.tell_area()
扩展性

4、property属性

'''
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
'''
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People('egon',75,1.85)
print(p1.bmi)
BMI指数
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
'''
#注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
圆的周长和面积

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

5、setter,deleter

class A:
    def __init__(self):
        self.__x = 10
    #返回__x的值
    @property
    def x(self):
        return self.__x
    #修改__x的值
    @x.setter
    def x(self,new_x):
        self.__x = new_x
    #删除__x的值
    @x.deleter
    def x(self):
        del self.__x
a = A()
print(a.x)
a.x = 20
print(a.x)
del a.x
print(a.x)
私有属性的查看修改删除

6、classmethod,staticmethod

class Student:
    f = open('student', encoding='utf-8')
    def __init__(self):
        pass

    def func(self):
        pass

    @classmethod   #类方法:默认参数cls,可以直接用类名调用,可以与类属性交互
    def show_student_info_class(cls):
        for line in cls.f:
            name, sex = line.strip().split(',')
            print(name, sex)

    @staticmethod   #静态方法  : 让类里的方法直接被类调用,就像正常的函数一样
    def show_student_info_static():
        f = open('student', encoding='utf-8')
        for line in f:
            name, sex = line.strip().split(',')
            print(name, sex)

Student.show_student_info_class()
Student.show_student_info_static()
类方法和静态方法
海宝,男
海博,女
海娇,男
海燕,女
海东,男
海峰,男
student文件

相同:都可以直接被类调用,不需要实例化

不同:类方法必须有一个cls参数表示这个类,可以使用类属性;静态方法不需要,静态方法不能直接使用

普通方法 默认有一个self对象传进来,并且只能被对象调用——绑定到对象

类方法 默认有一个cls传进来表示本类,并且可以被类和对象(不推荐)调用——绑定到类

静态方法 没有默认参数,并且可以被类和对象(不推荐)调用——非绑定

posted @ 2017-08-15 16:24  布吉岛丶  阅读(332)  评论(0编辑  收藏  举报