Python 面向对象基础

阅读目录

      • 面向过程与面向对象
        • 基本概念
        • 面向过程vs面向对象
      • 初识面向对象
        • 创建类和对象
        • 类的相关知识
        • 对象的相关知识
        • 对象之间的交互
      • 面向对象的三大特征
        • 封装
        • 继承
        • 多态

面向过程与面向对象

基本概念

面向过程和面向对象的区别:
Python 面向对象基础_第1张图片

什么是面向过程:
     自上而下顺序执行,逐步求精;
     其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构;
     各模块之间的关系尽可能简单,在功能上相对独立;
     每一模块内部均是由顺序、选择和循环三种基本结构组成;
     其模块化实现的具体方法是使用子程序。 程序流程在写程序时就已决定。 

什么是面向对象:
     把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。
     对同类对象抽象出其共性,形成类。
     类中的大多数数据,只能用本类的方法进行处理。
     类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。
     程序流程由用户在使用中决定。

理解面向对象:
     面向对象是相对面向过程而言
     面向对象和面向过程都是一种思想
     面向过程 强调的是功能行为 关注的是解决问题需要哪些步骤
     面向对象 将功能封装进对象,强调具备了功能的对象 关注的是解决问题需要哪些对象
     面向对象是基于面向过程的。 

面向对象的特点:
     是一种符合人们思考习惯的思想
     可以将复杂的事情简单化
     将程序员从执行者转换成了指挥者
     完成需求时: 先要去找具有所需的功能的对象来用。
                         如果该对象不存在,那么创建一个具有所需功能的对象。

类和对象的关系:
使用计算机语言就是不断的在描述现实生活中的事物。
Python中描述事物通过类的形式体现,类是具体事物的抽象,概念上的定义。
对象即是该类事物实实在在存在的个体
Python 面向对象基础_第2张图片

类的定义:
生活中描述事物无非就是描述事物的名称/属性和行为。
     如:人有身高,体重等属性,有说话,打架等行为。
Python中用类来描述事物也是如此
     属性:对应类中的成员变量。
     行为:对应类中的成员方法。
定义类其实在定义类中的成员(成员变量和成员方法)
拥有相同(或者类似)属性和行为的对象都可以抽像出一个类 

类的设计:
只需关注三项:
     事物名称(类名):人(Person)
     属性:身高(height)、年龄(age)
     行为(功能):跑(run)、打架(fight)


面向过程vs面向对象

'''
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,
	面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

 
 
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,
	上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。
面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,
	如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,
	每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),
	然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,
	又安排了一群神仙保驾护航,这些都是对象。
然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。
	如来根本不会管师徒四人按照什么流程去取。

面向对象的程序设计的
	优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,
		如对游戏中一个人物参数的特征和技能修改都很容易。

	缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程
		与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。
		于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,
	游戏等都是面向对象的程序设计大显身手的好地方。

在python 中面向对象的程序设计并不是全部。

面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,
	另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

了解一些名词:类、对象、实例、实例化

类:具有相同特征的一类事物(人、狗、老虎)

对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)

实例化:类——>对象的过程
'''
函数式编程 vs 面向对象:
	函数式:定义简单/调用简单
	面向对象:定义复杂/调用复杂  好处:归类,将某些类似的函数写在一起
		1. 函数式编程可能会比面向对象好.
		2. Python中支持两种编程方式.

初识面向对象

创建类和对象

面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,
	所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。

类就是一个模板,模板里可以包含多个函数,函数里实现一些功能

对象则是根据模板创建的实例,通过实例对象可以执行类中的函数

class是关键字,表示类
创建对象,类名称后加括号即可

Python 面向对象基础_第3张图片

类的相关知识

类有两种作用:属性引用和实例化

属性引用(类名.属性)
class Person:   # 定义一个人类
    role = 'person'  # 类变量,人的角色属性都是人
    def walk(self):  # 人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  # 用类去调用类变量,查看人的role属性
print(Person.walk)  # 引用人的走路方法,注意,这里不是在调用
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,
	可以用它来为每个实例定制自己的特征
class Person:   # 定义一个人类
    role = 'person'  # 人的角色属性都是人
    def __init__(self,name):
        self.name = name  # 每一个角色都有自己的昵称;
        
    def walk(self):  # 人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  # 查看人的role属性
print(Person.walk)  # 引用人的走路方法,注意,这里不是在调用
实例化的过程就是类——>对象的过程

原本我们只有一个Person类,在这个过程中,产生了一个egg对象,
	有自己具体的名字、攻击力和生命值。

语法:对象名 = 类名(参数)
egg = Person('egon')  # 类名()就等于在执行Person.__init__()
# 执行完__init__()就会返回一个对象。这个对象类似一个字典,
# --存着属于这个人本身的一些属性和方法。
查看属性&调用方法
print(egg.name)     # 查看属性直接 对象名.属性名
print(egg.walk())   # 调用方法,对象名.方法名()
关于 self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,
	你也可以给他起个别的名字,但是正常人都不会这么做。
类属性的补充
# 我们定义的类的属性到底存到哪里了?有两种方式查看
	dir(类名):查出的是一个名字列表
# 类名.__dict__:查出的是一个字典,key为属性名,value为属性值

# 特殊的类属性
	类名.__name__ # 类的名字(字符串)
	类名.__doc__ # 类的文档字符串
	类名.__base__ # 类的第一个父类(在讲继承时会讲)
	类名.__bases__ # 类所有父类构成的元组(在讲继承时会讲)
	类名.__dict__ # 类的字典属性
	类名.__module__ # 类定义所在的模块
	类名.__class__ # 实例对应的类(仅新式类中)

对象的相关知识

class Person:  # 定义一个人类
    role = 'person'  # 人的角色属性都是人

    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 每一个角色都有自己的昵称;
        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;
        self.life_value = life_value  # 每一个角色都有自己的生命值;

    def attack(self,dog):  
        # 人可以攻击狗,这里的狗也是一个对象。
        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
        dog.life_value -= self.aggressivity
对象是关于类而实际存在的一个例子,即实例
对象/实例只有一种作用:属性的引用
egg = Person('egon',10,1000)
print(egg.name)
print(egg.aggressivity)
print(egg.life_value)
# 当然了,你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,
# 我们也管它叫动态属性。
# 引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号
print(egg.attack)
def Person(*args,**kwargs):
    self = {}
    def attack(self,dog):
        dog['life_value'] -= self['aggressivity']

    def __init__(name,aggressivity,life_value):
        self['name'] = name
        self['aggressivity'] = aggressivity
        self['life_value'] = life_value
        self['attack'] = attack

    __init__(*args,**kwargs)
    return self

egg = Person('egon',78,10)
print(egg['name'])

定义及调用的固定模式

class 类名:
    def __init__(self,参数1,参数2):
        self.对象的属性1 = 参数1
        self.对象的属性2 = 参数2

    def 方法名(self):pass

    def 方法名2(self):pass

对象名 = 类名(1,2)  # 对象就是实例,代表一个具体的东西
                   # 类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
                   # 括号里传参数,参数不需要传self,其他与init中的形参一一对应
                   # 结果返回一个对象
对象名.对象的属性1   # 查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名()     # 调用类中的方法,直接用 对象名.方法名() 即可

对象之间的交互

class Person:  # 定义一个人类
    role = 'person'  # 人的角色属性都是人

    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 每一个角色都有自己的昵称;
        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;
        self.life_value = life_value  # 每一个角色都有自己的生命值;

    def attack(self,dog):
        # 人可以攻击狗,这里的狗也是一个对象。
        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
        dog.life_value -= self.aggressivity

class Dog:  # 定义一个狗类
    role = 'dog'  # 狗的角色属性都是狗

    def __init__(self, name, breed, aggressivity, life_value):
        self.name = name  # 每一只狗都有自己的昵称;
        self.breed = breed  # 每一只狗都有自己的品种;
        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;
        self.life_value = life_value  # 每一只狗都有自己的生命值;

    def bite(self,people):
        # 狗可以咬人,这里的狗也是一个对象。
        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
        people.life_value -= self.aggressivity

egg = Person('egon',10,1000)  # 创造了一个实实在在的人egg
ha2 = Dog('二愣子','哈士奇',10,1000)  # 创造了一只实实在在的狗ha2
print(ha2.life_value)         # 看看ha2的生命值
egg.attack(ha2)               # egg打了ha2一下
print(ha2.life_value)         # ha2掉了10点血

# 圆的计算
from math import pi

class Circle:
    '''
    定义了一个圆形类;
    提供计算面积(area)和周长(perimeter)的方法
    '''
    def __init__(self,radius):
        self.radius = radius

    def area(self):
         return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi *self.radius


circle =  Circle(10) # 实例化一个圆
area1 = circle.area() # 计算圆面积
per1 = circle.perimeter() # 计算圆周长
print(area1,per1) # 打印圆面积和周长

面向对象的三大特征

封装

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。

所以,在使用面向对象的封装特性时,需要:

	将内容封装到某处
	从某处调用被封装的内容

第一步:将内容封装到某处

# self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1

# 当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2

# 所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,
# --在内存里类似于下图来保存。

Python 面向对象基础_第4张图片

第二步:从某处调用被封装的内容

调用被封装的内容时,有两种情况:
	通过对象直接调用
	通过self间接调用
# 通过对象直接调用被封装的内容

# 上图展示了对象 obj1 和 obj2 在内存中保存的方式,
# --根据保存格式可以如此调用被封装的内容:对象.属性名

class Foo:
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
obj1 = Foo('min', 18)
print(obj1.name)    # 直接调用obj1对象的name属性
print(obj1.age)     # 直接调用obj1对象的age属性
 
obj2 = Foo('jaychou', 23)
print(obj2.name)    # 直接调用obj2对象的name属性
print(obj2.age)     # 直接调用obj2对象的age属性
# 通过self间接调用被封装的内容

# 执行类中的方法时,需要通过self间接调用被封装的内容

class Foo:
  
    def __init__(self, name, age):
        self.name = name
        self.age = age
  
    def detail(self):
        print(self.name)
        print(self.age)
  
obj1 = Foo('min', 18)
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),
# --所以,此时方法内部的 self = obj1,即:self.name 是 min ;self.age 是 18
  
obj2 = Foo('jaychou', 73)
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),
#--所以,此时方法内部的 self = obj2,即:self.name 是 jaychou ; self.age 是 78

综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,
然后通过对象直接或者self间接获取被封装的内容。

# 封装:对变量的封装、对方法的封装

# 将相关功能封装到一个类中:将函数写入类中就是对方法的封装
class Message:
    def email(self): pass

    def msg(self): pass

    def wechat(self): pass


# 将数据封装到一个对象中:将变量封装到对象中
class Person:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
        self.money = 1000  # 这样不通过参数进行传递,表示,创建任何一个对象,自带money属性,值都是一样的
        print("方法里的self是%s" % (self))


obj = Person('康娜', 18, '女')
# self 参数就是实例化后的对象,自动传入
obj2 = Person('托尔', 18, '女')
print(obj)
print(obj2)  # 和对象值是一样的,有几个对象,self就会分别自动传入几次

一个巧用:默认值参数,如果没有设置就给它一个空列表;如果设置了,就放入传入的参数;三目运算符的妙用

Python 面向对象基础_第5张图片

继承

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,
父类又可称为基类或超类,新建的类称为派生类或子类

python中类的继承分为:单继承和多继承
class ParentClass1: # 定义父类
    pass

class ParentClass2: # 定义父类
    pass

class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): # python支持多继承,用逗号分隔开多个继承的类
    pass

查看继承

>>> SubClass1.__bases__ # __base__只查看从左到右继承的第一个子类,
# __bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

# 如果没有指定基类,python的类会默认继承object类,
# object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

继承与抽象(先抽象再继承)

# 抽象即抽取类似或者说比较像的部分。

# 抽象分成两个层次: 

# 1.将C罗和梅西这俩对象比较像的部分抽取成类; 

# 2.将人,猪,狗这三个类比较像的部分抽取成父类。

# 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

Python 面向对象基础_第6张图片

# 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,
# --才能通过继承的方式去表达出抽象的结构。

# 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

Python 面向对象基础_第7张图片

继承与重用性

class Animal:

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = '猫'

    def climb(self):
        print('爬树')

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed='狗'

    def look_after_house(self):
        print('汪汪叫')


# ######### 执行 #########

c1 = Cat('小白家的小黑猫')
c1.eat()

c2 = Cat('小黑的小白猫')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()
# 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,
# 但是类B的大部分内容与类A的相同时
# 我们不可能从头开始写一个类B,这就用到了类的继承的概念。
# 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的昵称;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    pass

class Person(Animal):
    pass

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
egg.eat()
ha2.eat()
'''
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,
	大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,
	比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,
	意义重大.
'''

派生

# 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),
# --需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,
# --就以自己为准了。

class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的昵称;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    '''
    狗类,继承Animal类
    '''
    def bite(self, people):
        '''
        派生:狗有咬人的技能
        :param people:  
        '''
        people.life_value -= self.aggressivity

class Person(Animal):
    '''
    人类,继承Animal
    '''
    def attack(self, dog):
        '''
        派生:人有攻击的技能
        :param dog: 
        '''
        dog.life_value -= self.aggressivity

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)

# 注意:像ha2.life_value之类的属性引用,会先从实例中找life_value然后去类中找,
# --然后再去父类中找...直到最顶级的父类。
'''
在子类中,新建的重名的函数属性,在编辑函数内部功能时,
有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,
即:类名.func(),此时就与调用普通函数无异了,
因此即便是self参数也要为其传值.

在python3中,子类执行父类的方法也可以直接用super方法.
'''
class A:
    def hahaha(self):
        print('A')

class B(A):
    def hahaha(self):
        super().hahaha() # 此时执行的是父类中的同名方法
        # super(B,self).hahaha() 前面的参数含义:当前类,当前类的实例对象,去执行父类中同名的方法
        # A.hahaha(self) 这个self还是 B的 实例化对象,需要主动传入
        print('B')

a = A()
b = B()
b.hahaha() # A B 
super(B,b).hahaha() # A
class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的昵称;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    '''
    狗类,继承Animal类
    '''
    def __init__(self,name,breed,aggressivity,life_value):
        super().__init__(name, aggressivity, life_value) # 执行父类Animal的init方法
        self.breed = breed  # 派生出了新的属性

    def bite(self, people):
        '''
        派生出了新的技能:狗有咬人的技能
        :param people:  
        '''
        people.life_value -= self.aggressivity

    def eat(self):
        # Animal.eat(self)
        #super().eat()
        print('from Dog')

class Person(Animal):
    '''
    人类,继承Animal
    '''
    def __init__(self,name,aggressivity, life_value,money):
        #Animal.__init__(self, name, aggressivity, life_value)
        #super(Person, self).__init__(name, aggressivity, life_value)
        super().__init__(name,aggressivity, life_value)  # 执行父类的init方法
        self.money = money   # 派生出了新的属性

    def attack(self, dog):
        '''
        派生出了新的技能:人有攻击的技能
        :param dog: 
        '''
        dog.life_value -= self.aggressivity

    def eat(self):
        #super().eat()
        Animal.eat(self)
        print('from Person')

egg = Person('egon',10,1000,600)
ha2 = Dog('二愣子','哈士奇',10,1000)
print(egg.name)
print(ha2.name)
egg.eat()

通过继承建立了派生类与基类之间的关系,它是一种’是’的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师

>>> class Teacher:
...     def __init__(self,name,gender):
...         self.name=name
...         self.gender=gender
...     def teach(self):
...         print('teaching')
... 
>>> 
>>> class Professor(Teacher):
...     pass
... 
>>> p1=Professor('egon','male')
>>> p1.teach()
teaching

抽象类与接口类

# 接口类

# 继承有两种用途:

# 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)  

# 二:声明某个子类兼容于某基类,定义一个接口类Interface,
# --接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,
# --子类继承接口类,并且实现接口中的功能

class Alipay:
    '''
    支付宝支付
    '''
    def pay(self,money):
        print('支付宝支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)


def pay(payment,money):
    '''
    支付函数,总体负责支付
    对应支付的对象和要支付的金额
    '''
    payment.pay(money)


p = Alipay()
pay(p,200)



# 开发中容易出现的问题 
class Alipay:
    '''
    支付宝支付
    '''
    def pay(self,money):
        print('支付宝支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)

class Wechatpay:
    def fuqian(self,money):
        '''
        实现了pay的功能,但是名字不一样
        '''
        print('微信支付了%s元'%money)

def pay(payment,money):
    '''
    支付函数,总体负责支付
    对应支付的对象和要支付的金额
    '''
    payment.pay(money)


p = Wechatpay()
pay(p,200)   # 执行会报错,它的实例要执行payment.fuqian()


# 接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题

class Payment:
    def pay(self):
        raise NotImplementedError # 子类必须重写这个方法,否则抛异常

class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)


p = Wechatpay()  # 这里不报错
pay(p,200)      # 这里报错了

# 借用abc模块来实现接口
from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass


class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)

p = Wechatpay() # 不调就报错了
"""
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,
使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”
——这在程序设计上,叫做归一化。

归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,
所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,
当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
"""
# 依赖倒置原则:
# 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。
# 换言之,要针对接口编程,而不是针对实现编程

# 在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,
# 子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:

# http://pypi.python.org/pypi/zope.interface

# twisted的twisted\internet\interface.py里使用zope.interface

# 文档https://zopeinterface.readthedocs.io/en/latest/

# 设计模式:https://github.com/faif/python-patterns

'''
# 为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,
从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,
这极大地降低了使用者的使用难度。

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,
松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,
即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,
他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,
开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

'''

抽象类

'''
什么是抽象类:

    与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,
    它的特殊之处在于只能被继承,不能被实例化

为什么要有抽象类:

    如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,
    内容包括数据属性和函数属性。

	比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,
	你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

    从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

	从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,
	只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
'''

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('硬盘数据的读取方法')

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

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

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

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

抽象类与接口类

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

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

在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
'''

# 多继承问题
'''
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口

接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
'''
# 方法的实现
'''
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
'''

多继承 顺序

Python 面向对象基础_第8张图片

继承原理

# python到底是如何实现继承的,对于你定义的每一个类,
# python会计算出一个方法解析顺序(MRO)列表,
# 这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
>>> F.mro() #等同于F.__mro__
[<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.如果对下一个类存在两个合法的选择,选择第一个父类
'''

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性

# 新式类继承顺序:F->D->B->E->C->A
# 经典类继承顺序:F->D->B->A->E->C
# python3中统一都是新式类
# pyhon2中才分新式类与经典类
class A(object):
     pass

class B(A):
     pass

class C(B):
     pass

class D(object):
     pass

class E(D,C):
     pass

class F(object):
     pass

class G(F):
     pass

class H(C,G):
     pass

class Foo(E,H):
     pass


# print(E.__mro__)
# print(H.__mro__)
"""
L(Foo + L(E)  + L(H) )

#=(D,OBJ)+(C,B,A,OBJ)
L(E) = E,D,C,B,A,OBJ

#=(C,B,A,OBJ)+(G,F,OBJ)
L(H) = H,C,B,A,G,F,OBJ

Foo = (E,D,C,B,A,OBJ) + (H,C,B,A,G,F,OBJ)
Foo,E,D,H,C,B,A,G,F,OBJ
"""
print(Foo.__mro__)

# 算法顺序:
'''
1.取左边最左侧元素 和 右边除了最左侧元素 进行比较,如果这个元素,
	和右边除了最左侧元素还存在相同的,则让它保持不动,
    此时从右边元素开始找,取出右边最左侧元素;然后再回到左侧判断
2.直到左边只剩一个元素,如果右边存在除了最左侧元素外,还有和它相同的,怎右边等待,
	此时从右边元素开始找,取出右边最左侧    
	元素,然后一直取,直到和左边最左侧元素相同的那个为止,取出这个元素!
'''

通过习题,来 深入理解 继承


class Base:
    def f1(self):
         print('base.f1')

class Foo(Base):
    def f2(self):
         print('foo.f2')


# 1. 是否执行
obj = Foo()
obj.f2()
obj.f1()

# 2. 是否执行
obj = Base()
obj.f1()
obj.f2() # 错

------
"""
class Base:
     def f1(self):
          print('base.f1')

class Foo(Base):
     def f3(self):
          print('foo.f3')

     def f2(self):
          print('foo.f2')
          self.f3() # obj是那一个类(Foo),那么执行方法时,就从该类开始找.

obj = Foo()
obj.f2() # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
"""
------
"""
class Base:
     def f1(self):
          print('base.f1')

     def f3(self):
          print('foo.f3')

class Foo(Base):

     def f2(self):
          print('foo.f2')
          self.f3()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.


obj = Foo()
obj.f2()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
"""
------
"""
class Base:
     def f1(self):
          print('base.f1')

     def f3(self):
          self.f1() # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
          print('foo.f3')

class Foo(Base):

     def f2(self):
          print('foo.f2')
          self.f3()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.


obj = Foo()
obj.f2()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
"""
------
"""
class Base:
     def f1(self):
          print('base.f1')

     def f3(self):
          self.f1() # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
          print('base.f3')

class Foo(Base):
     def f1(self):
          print('foo.f1')

     def f2(self):
          print('foo.f2')
          self.f3()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.


obj = Foo()
obj.f2()  # obj是那一个类(Foo),那么执行方法时,就从该类开始找.
# 结果:foo.f2 foo.f1 base.f3

obj2 = Base()
obj2.f3()

多态

# 同一个对象的多种形态(多种形态或多种状态)
     # 比如 猫,从猫的角度来看,也可以从动物的角度,生物角度...来看
     # 鸭子模型,只要可以嘎嘎叫就是鸭子.

     Python
     # 由于python原生支持多态,所以没有特殊性.
     
class Animal(object):
     def eat(self):
          print("动物会吃东西")

class Tiger(Animal):
     def eat(self):
          print("老虎要吃肉")
class Elephant(Animal):
     def eat(self):
          print("大象吃香蕉")

# def feed_tiger(tiger):
#     tiger.eat()
# def feed_elephant(elephant):
#     elephant.eat()

# 不需要为了实现某个重写的方法,而各自声明独立的方法,已经传入特定的参数

def feed(arg/animal): # 这个arg可以接受各种类型的数据
     arg.eat()
     animal.eat()

t=Tiger()
e=Elephant() # 多态的实质:这里老虎是老虎的对象,到传入feed后,变成参数的类来看了

#feed_tiger(t)
#feed_elephant(e) # 老虎要吃肉 大象吃香蕉 ;会实现调用各自重写的方法

feed(t)
feed(e)

# 面向对象的核心就是多态;作用就是:超强的可扩展性

你可能感兴趣的:(Python学习)