Python基础(17)_面向对象程序设计(抽象类、继承原理、封装、多态,绑定方法)

一、抽象类

  抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

1、在python中实现抽象类

import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')
    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')
    def write(self):
        print('硬盘数据的读取方法')

wenbenwenjian=Txt()
yingpanwenjian=Sata()


#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()

2、抽象类与接口

  抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

  抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 

二、继承的实现原理(继承顺序)

1、在python中,如果类继承了多个类,那么其寻找的方式有两种:分别是:广度优先和深度优先

  当类是新式类是,多继承情况下,会按照广度优先方式查找

  当类是经典类是,多继承情况下,会按照深度优先方式查找

假如有A-H类的继承关系如下:

Python基础(17)_面向对象程序设计(抽象类、继承原理、封装、多态,绑定方法)

  

#新式类:
class A(object):
    def test(self):
        print('from A')
    pass
class B(A):
    # def test(self):
    #     print('from B')
    pass
class C(A):
    # def test(self):
    #     print('from C')
    pass
class D(A):
    # def test(self):
    #     print('from D')
    pass
class E(B):
    # def test(self):
    #     print('from E')
    pass
class F(C):
    # def test(self):
    #     print('from F')
    pass
class G(D):
    # def test(self):
    #     print('from G')
    pass
class H(E,F,G):
    # def test(self):
    #     print('from H')
    pass
h=H()
# h.test=1
# print h.__dict__
#新式类的在这中继承结构下,属性的查找关系
# H->E->B->F->C-G-D-A 广度优先


#经典类的在这中继承结构下,属性的查找关系
# H-E-B-A-F-C-G-D 深度优先

 2、继承原理

   Python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

1 >>> F.mro() #等同于F.__mro__
2 [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

  为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
  而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

3、子类中调用父类方法

有两种方法:

  1、People.__init__(self,name,age,sex)    #指名道姓地调用People类的__init__函数

  2、super().__init__(name,age,sex)           #调用父类的__init__的功能,实际上用的是绑定方法,super只查找一次

class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def foo(self):
        print('from parent')

class Teacher(People):
    def __init__(self,name,age,sex,salary,level):
        # People.__init__(self,name,age,sex) #指名道姓地调用People类的__init__函数

        #在python3中
        super().__init__(name,age,sex) #调用父类的__init__的功能,实际上用的是绑定方法,super只查找一次
       self.salary=salary self.level=level def foo(self): super().foo() print('from child') t=Teacher('egon',18,'male',3000,10) # print(t.name,t.age,t.sex,t.salary,t.level) t.foo()

  

三、封装

1、封装不是单纯意义的隐藏:

  1:封装数据的主要原因是:保护隐私

  2:封装方法的主要原因是:隔离复杂度

2、封装分为两个层面 

   1)第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装;对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

  2)第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。    

    在python中用双下划线的方式实现隐藏属性(设置成私有的)

    类中所有双下划线开头的名称,如__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的形式访问到.,在类的外部,无法直接使用变形的属性

#在外部访问隐藏函数方式:
t=A()
t._A__foo()

这种自动变形的特点:

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

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

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

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

 3、property结合使用的封装

class People:
    def __init__(self,name,age,sex,height,weight,permission=False):
        self.__name=name
        self.__age=age
        self.__sex=sex
        self.__height=height
        self.__weight=weight
        self.permission=permission

    @property           #只读
    def name(self):
        return self.__name

    @name.setter        #可写
    def name(self,val):
        if not isinstance(val,str):
            raise TypeError('名字必须为字符串')
        self.__name=val

    @name.deleter       #可删除
    def name(self):
        if not self.permission:
            raise PermissionError('权限不够')
        del self.__name

    @property
    def bmi(self):
        return self.__weight/(self.__height**2)

    def tell_info(self):
        print('''
        --------%s  info----------
        Name:%s
        Age:%s
        Sex:%s
        Height:%s
        Weight:%s
        --------------------------
        '''%(self.__name,self.__name,self.__age,self.__sex,self.__height,self.__weight))

egon=People('egon',18,'male',1.76,72)
egon.tell_info()
print(egon.name)
egon.name='alex'
print(egon.name)
# egon.permission=True
# del egon.name
print(egon.bmi)

  

 四、 多态与多态性

1、多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

  1. 序列类型有多种形态:字符串,列表,元组。

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

 1 import abc
 2 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
 3     @abc.abstractmethod
 4     def talk(self):
 5         pass
 6 
 7 class People(Animal): #动物的形态之一:人
 8     def talk(self):
 9         print('say hello')
10 
11 class Dog(Animal): #动物的形态之二:狗
12     def talk(self):
13         print('say wangwang')
14 
15 class Pig(Animal): #动物的形态之三:猪
16     def talk(self):
17         print('say aoao')

2、多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同功能的函数。

  在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

 1 >>> def func(animal): #参数animal就是对态性的体现
 2 ...     animal.talk()
 3 ... 
 4 >>> people1=People() #产生一个人的对象
 5 >>> pig1=Pig() #产生一个猪的对象
 6 >>> dog1=Dog() #产生一个狗的对象
 7 >>> func(people1) 
 8 say hello
 9 >>> func(pig1)
10 say aoao
11 >>> func(dog1)
12 say wangwang

3、多态性的好处

  1.增加了程序的灵活性

    以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

  2.增加了程序额可扩展性

    通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用     

 1 >>> class Cat(Animal): #属于动物的另外一种形态:猫
 2 ...     def talk(self):
 3 ...         print('say miao')
 4 ... 
 5 >>> def func(animal): #对于使用者来说,自己的代码根本无需改动
 6 ...     animal.talk()
 7 ... 
 8 >>> cat1=Cat() #实例出一只猫
 9 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
10 say miao
11 
12 '''
13 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
14 '''

四、 绑定方法

类中定义的函数分成两大类:

一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

  1. 绑定到类的方法:用classmethod装饰器装饰的方法。 为类量身定制类.boud_method(),自动将类当作第一个参数传入其实对象也可调用,但仍将类当作第一个参数传入

  2. 绑定到对象的方法:没有被任何装饰器装饰的方法。为对象量身定制对象.boud_method(),自动将对象当作第一个参数传入

  (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

二:非绑定方法:用staticmethod装饰器装饰的方法

     1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

  注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说

 

绑定方法:绑定给谁就是给谁用的

class People:
    def __init__(self,name):
        self.name=name
    def bar(self):
        print('--->',self.name)

    @classmethod
    def func(cls):
        print(cls)
f=People('egon')

# print(People.func) #绑定给类
# print(f.bar) #绑定给对象的

# People.func()
# f.func()

2、 staticmethod:不与类或对象绑定,谁都可以调用,没有自动传值效果,python为我们内置了函数staticmethod来把类中的函数定义成静态方法

import hashlib
import time
class MySQL:
    def __init__(self,host,port):
        self.id=self.create_id()
        self.host=host
        self.port=port
    @staticmethod
    def create_id(): #就是一个普通工具
        m=hashlib.md5(str(time.clock()).encode('utf-8'))
        return m.hexdigest()

print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数

3、 classmethod:classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法

HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:UsersAdministratorPycharmProjects	est面向对象编程	est1db'
settings.py内容
import settings
import hashlib
import time
class MySQL:
    def __init__(self,host,port):
        self.host=host
        self.port=port

    @classmethod
    def from_conf(cls):
        print(cls)
        return cls(settings.HOST,settings.PORT)

print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()

print(conn.host,conn.port)
conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类