day6-面向对象基础篇
一、面向对象引子及概念
结合编程的一些理论知识和实践,可以总结出目前存在以下编程模式:
1. 面向过程
按照业务逻辑和实现过程步骤来逐步垒代码,代码编写的逻辑即对应于实际实现的步骤过程,核心是过程两个字,从代码执行顺序上体现出设计者的逻辑过程,整个程序就是把若干个过程串起来的效果。本质上像是构建了一条生成流水线,每一道工序都通过代码块严格定义。
优点:
复杂问题简单化,把大的任务逐步分解成一个一个小的任务分步实现,实现了每个小的步骤即可完成整体任务。逻辑思想符合日常生活中的常规过程化思维,因而代码可读性较高。
缺点:
由于实现逻辑是自上而下的分步过程,任何一个环节的变动,都可能涉及到后续环节的变动,牵一发而动全身。因此代码的灵活性、可维护性较差:构建的生产面包的流水线,如果原料工艺发生变化,很可能要进行生产线的改造;当然想用它来生产饮料,几乎要推倒重来。
应用场景:
需求相对固化,不会经常变更,容易通过过程化思维顺序体现出来的情况。
2. 函数式编程
函数式编程也是一种面向过程的编程范式,主要变化在于把重要的功能逻辑通过函数进行封装,以便重复调用,同时也提高了代码的维护性。其优点就不赘述了,缺点除了牵一发而动全身外(函数的定义发生变化,至少调用它的每个地方都要随之改变),对于较为复杂的应用场景,遇到多个函数需要传递共同的参数时,你就会觉得写起来真是麻烦又啰嗦。如果要实现一个数据库连接查询修改的程序,数据库的连接参数是共性参数,如果根据数据库增删改查的不同分别定义函数,数据库连接参数就要重复多次,而如果通过一个函数来定义并区分,你还得各种if去判断要执行的sql到底属于哪一种。
总之函数式编程不是万能的,有时候虽然能实现结果,但实现的过程可能比较曲折。
3. 面向对象
核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
这部分文字摘抄自http://www.cnblogs.com/linhaifeng/articles/6182264.html,是目前接触到的解释接比较接地气的说法。个人理解,面向对象是一个抽象归类的思维过程,先梳理出具有共同属性的个体对象,然后对他们抽象归类。这样整个编程的设计思想不再是面向过程中的自上而下的串联顺序关系,而变成一种属于和不属于,具有或者不具有(某种属性)的关系。
优点:
对于复杂的场景,功能实现的灵活性更好,换言之适配性和扩展性更好。
缺点:
面向对象是一个抽象的过程,不同于面向对象的流水线式顺序执行过程,编程的复杂程度更高,首要前提是要梳理出对象之间的关系,归类出相应的属性,对于开发者而言要求更高,需具备一定的储备之后才能胜任一般场景的编程开发。
应用场景:
需求经常变化且相对复杂的软件,一般需求的变化都集中在用户层,互联网应用,游戏等都是面向对象的程序设计大显身手的好地方。
二、类和对象
2.1 类和对象的基本概念
梳理一下类和对象的概念:
类更像是一个模板,是对众多具备相似特征(属性)和技能(方法/函数)的统一集中实现,略显抽象(不做实例化似乎感受不到它的存在)
对象是根据类这个模板创建的实例,是对具备某种特征(属性)和技能(方法/函数)的具体化个体式的实现(实例化的对象作为一个独立的个体存在)。有了对象这一具体的实例,才能让类中描述的特征(属性)和技能(方法/函数)能真正落地体现出来,否则类只是构想中的空中楼阁。
2.2 类和对象的关系
在面向对象的编程范式下,我们需要通过类和对象来实现编程设计,因此面向对象是对类和对象的应用。
以下文字转自海峰老师博客http://www.cnblogs.com/linhaifeng/articles/6182264.html
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看
- 在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
- 在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
转述一段形象解释类和对象的关系的文字,出处http://blog.csdn.net/Four_Infinite/article/details/52795877
• “类提供默认行为,是实例的工厂”,我觉得这句原话非常经典,一下道破了类和实例的关系。看上面代码,体会一下,是不是这个理?所谓工厂,就是可以用同一个模子做出很多具体的产品。类就是那个模子,实例就是具体的产品。所以,实例是程序处理的实际对象。
• 类是由一些语句组成,但是实例,是通过调用类生成,每次调用一个类,就得到这个类的新的实例。
2.3 创建类和对象
图片转自https://www.cnblogs.com/wupeiqi/p/4493506.html
- class是关键字,表示类
- 创建对象,类名称后加括号即可
ps: 类中的函数第一个参数必须是self(下文会详述类的三大特性)
类中定义的函数叫做 “方法”,但__init__有特殊的叫法,称为构造函数,或构造方法,或初始化方法,用于定义类的原始属性,在实例化类时自动调用
类名首字母大写,这是约定俗成的编程习惯,遵照执行即可
2.4 构造函数
上文提到了构造函数__init__(),又称之为苟晗函数或构造方法,这里有必要展开阐述一下。
构造函数是一种特殊类型的方法(函数),它在类的实例化对象时被调用。 构造函数通常用于初始化(赋值)给实例变量。它在生成对象时被调用,在创建一个类时,有一些在实例化时必须绑定某些属性时,可以定义一个__init__方法将其强制填写进去。
先来简单的栗子:
1 # !/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 __author__ = 'Maxwell' 4 5 class Person: 6 def __init__(self, name, age, gender): 7 self.name = name 8 self.age = age 9 self.gender = gender 10 11 person1 = Person('ZhangSan', '18', 'man') 12 print('name:%s\t age:%s\t gender:%s' %(person1.name, person1.age, person1.gender)) 13 14 运行结果: 15 name:ZhangSan age:18 gender:man 16
还是引用一段解释比较到位的文字来阐述吧(原文出处http://blog.csdn.net/Four_Infinite/article/details/52795877)
我们需要先明确一个基本概念,类就是一种对象类型,和跟前面学习过的数值、字符串、列表等等类型一样。比如这里构建的类名字叫做Person,那么就是我们要试图建立一种对象类型,这种类型被称之为Person,就如同有一种对象类型是list一样。
在构建Person类的时候,首先要做的就是对这种类型进行初始化,也就是要说明这种类型的基本结构,一旦这个类型的对象被调用了,第一件事情就是要套用这个类型的基本结构,也就是类Person的基本结构。就好比我们每个人,在头脑中都有关于“人”这样一个对象类型(对应着类),一旦遇到张三(张三是一个具体人),我们首先套用“人”这个类的基本结构:一个鼻子两只眼,鼻子下面一张嘴。如果张三符合这个基本机构,我们不会感到惊诧(不报错),如果张三不符合这个基本结构(比如三只眼睛),我们就会感到惊诧(报错了)。
由于类是我们自己构造的,那么基本结构也是我们自己手动构造的。在类中,基本结构是写在__init__()这个函数里面。故这个函数称为构造函数,担负着对类进行初始化的任务。
还是回到Person这个类,如果按照上面的代码,写好了,是不是__init()__就运行起来了呢?不是!这时候还没有看到张三呢,必须看到张三才能运行。所谓看到张三,看到张三这样一个具体的实实在在的人,此动作,在python中有一个术语,叫做实例化。当类Person实例化后立刻运行__init()__函数。
注意:
- 构造函数用于实例化后对对象进行初始化,但类中构造函数不是必须要显式定义的,需要的时候才定义,我们可以把类中需要自动绑定的一些属性通过构造函数来初始化实现,但也并非必须这么做。
- 当创建没有构造函数的类时,Python会自动创建一个不执行任何操作的默认构造函数。
- 每个类必须有一个构造函数,即使它只依赖于默认构造函数。
- 只要实例化一个类(创建对象),构造函数就被会自动调用,不管它是人为显式定义的,还是调用的默认的空的构造函数
当然,大多数情况下,我们还是会通过显式定义构造函数,来为类定义一些必要的属性。
2.5 self关键字
细心的朋友会发现在构造函数中会出现一个self关键字,定义某一属性时会通过self.属性 = 属性来实现,并且这样定义后,创建出一个对象时传入的属性参数就真的绑定到对象上去了,为什么可以这么神奇呢?
python类中的self关键字相当于java中的this关键字,它代表类的实例对象本身,创建一个什么对象self就代表那个对象。这个关键字在构造函数中不能省略,在类中的其他方法中如果有需要调用到对象,也不能省略,但是在创建对象时,以及之后通过对象调用类中的方法时,self参数会自动传入,无需手动填写。有一个说法是self关键字也是约定俗成这么写了,如果非要用其他关键字来代替,理论上也可以,不过太另类了。
以下实例和文字分析转自http://www.cnblogs.com/xiangjun555/p/6992374.html
1 class dog(object): #用class定义类 2 "dog class" #对类的说明 3 4 def __init__(self,name): #构造函数或者是构造方法,也可以叫初始化方法 5 self.name = name 6 7 8 def sayhi(self): #类方法 9 "sayhi funcation" #对类方法的说明 10 11 print("hello,i am a dog,my name is ",self.name) 12 13 14 d = dog("alex") #定义一个d的对象,叫实例 15 d.sayhi() #调用实例的方法
self的传递过程如下:
其实self 这个关键字相当于实例化对象本身(self相当于d),在实例化过程中,把自己传进去了,我们对上面的两行做一下解释:
2.6 类的实例化过程分析
有了以上章节的基础,下面我们可以来总结一下类的实例化过程了,先上图把(转自http://www.cnblogs.com/xiangjun555/p/6992374.html)
用文字概述起来就是:
1. 通过实例化语句调用类(就像调用函数一样),在内存中开辟一块空间,为其命名
2. 把对象的地址和参数传入构造函数,实现必要属性的绑定
3. 返回创建的对象(将各个属性保存到对象中)
2.7 对象和类在内存中如何保存
以下内容转自https://www.cnblogs.com/wupeiqi/p/4493506.html
类和类中定义的方法,在内存中只保存一份, 而基于类实例化创建的每个对象,都需要在内存中存一份,如下图所示:
如上图所示,根据类创建对象时,对象中除了封装 name 和 age 的值之外,还会保存一个类对象指针,该值指向当前对象的类。
当通过 obj1 执行 【方法一】 时,过程如下:
- 根据当前对象中的类对象指针 找到类中的方法(方法保存在类中)
- 将对象 obj1 当作参数传给 方法的第一个参数 self
2.8 析构函数__del__()
析构函数__del__()有点儿跟构造函数反着干的意思,用于对变量、对象的删除处理,删除变量、对象到内存的引用关系,进而触发python的垃圾回收机制回收内存(定期检测没有被引用的内存地址,然后清除)。当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。__del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数
三、面向对象三大特性
面向对象的三大特性是指:封装、继承和多态。
3.1 封装
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的。这段论述转自http://www.cnblogs.com/linhaifeng/articles/7340801.html
最好的例证是即便我们在类中设置了私有属性,我们还是可以通过类名和属性名称拼接出来去访问它(具体例子就不赘述了,网上很多),因此隐藏并非封装的目的。
还是继续引用大神的博客(没办法,人写的好啊),出处http://www.cnblogs.com/linhaifeng/articles/7340801.html:
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???封装后调用者无需关心它的内部逻辑细节,只需要按照规定的方式调用即可。
封装分为封装属性和封装方法,我们分开阐述把:
1. 封装数据属性
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制(有些地方会强调防止数据被随意修改),以此完成对数据属性操作的严格控制。
1 class Teacher: 2 def __init__(self,name,age): 3 # self.__name=name 4 # self.__age=age 5 self.set_info(name,age) 6 7 def tell_info(self): 8 print('姓名:%s,年龄:%s' %(self.__name,self.__age)) 9 def set_info(self,name,age): 10 if not isinstance(name,str): 11 raise TypeError('姓名必须是字符串类型') 12 if not isinstance(age,int): 13 raise TypeError('年龄必须是整型') 14 self.__name=name 15 self.__age=age 16 17 18 t=Teacher('egon',18) 19 t.tell_info() 20 21 t.set_info('egon',19) 22 t.tell_info()
2. 封装方法
目的是隔离复杂度
封装方法举例:
(1). 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
(2). 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
(3). 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
上一个实际例子:
在日常生活中,取款是一个功能,而这个功能有很多功能组成:插卡、密码验证、输入金额、打印账单、取款,但是对于使用者来说,只需要知道取款这个功能就可以了,其余的功能我们都隐藏起来,很明显这样做就隔离了复杂度,同时也提升了安全性
1 class ATM: 2 def __card(self): 3 print('插卡') 4 def __auth(self): 5 print('用户认证') 6 def __input(self): 7 print('输入取款金额') 8 def __print_bill(self): 9 print('打印账单') 10 def __take_money(self): 11 print('取款') 12 13 def withdraw(self): 14 self.__card() 15 self.__auth() 16 self.__input() 17 self.__print_bill() 18 self.__take_money() 19 20 a=ATM() 21 a.withdraw() 22 23 # 运行结果为 24 插卡 25 用户认证 26 输入取款金额 27 打印账单 28 取款
好了,明确了封装的意义,下面我们来看看封装的具体过程以及如何使用封装大法。以下内容转自https://www.cnblogs.com/wupeiqi/p/4493506.html
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 先将内容封装到某处
- 从某处调用被封装的内容
第一步:将内容封装到某处
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
这里也说明普通属性的值,是封装到了对象中,而属性名称和方法,则封装在类中。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 obj1 = Foo('wupeiqi', 18) 8 print obj1.name # 直接调用obj1对象的name属性 9 print obj1.age # 直接调用obj1对象的age属性 10 11 obj2 = Foo('alex', 73) 12 print obj2.name # 直接调用obj2对象的name属性 13 print obj2.age # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def detail(self): 8 print self.name 9 print self.age 10 11 obj1 = Foo('wupeiqi', 18) 12 obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 13 14 obj2 = Foo('alex', 73) 15 obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78 16
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容(属性)封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
3.2 继承
一下文字转自https://www.cnblogs.com/wupeiqi/p/4493506.html和http://www.cnblogs.com/linhaifeng/articles/7340153.html,有改动
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。子类会“”遗传”父类的属性和方法,从而解决代码重用问题(多个子类共有的属性可以提取到父类中以便重用,多方法也是属性,后续会专门阐述属性)。
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:
1 class cat: 2 def yell(self): 3 print('MiaoMiao') 4 def eat(self): 5 print('eating') 6 def shit(self): 7 print('shitting') 8 def pee(self): 9 print('pee') 10 11 class dog: 12 def yell(self): 13 print('WangWang') 14 def eat(self): 15 print('eating') 16 def shit(self): 17 print('shitting') 18 def pee(self): 19 print('pee')
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
1 class Animal(): 2 def __init__(self, name): 3 self.name = name 4 def eat(self): 5 print('%s is eating' % self.name) 6 def shit(self): 7 print('%s is shitting' % self.name) 8 def pee(self): 9 print('%s is peing' % self.name) 10 11 class Cat(Animal): 12 def yell(self): 13 print('MiaoMiao') 14 15 class Dog(Animal): 16 def yell(self): 17 print('WangWang') 18 19 cat1 = Cat('Baimao') 20 dog1 = Dog('XiaoHuang') 21 22 运行结果: 23 Baimao is eating 24 XiaoHuang is eating 25 cat1.eat() 26 dog1.eat()
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
注:除了子类和父类的称谓,你可能看到过 派生类 和 基类 ,他们与子类和父类只是叫法不同而已。
那么问题来了,如果子类从父类集成了某个属性,但是又稍有改动,怎么办呢?这就不得不采用先继承再重构的思路了,即先从父类继承某一属性,然后在自己的类中重新定义该属性,实现重构。需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了,并且整个重构过程不影响父类。
1 class Animal(): 2 owner = 'human' #公有属性 3 def __init__(self, name): 4 self.name = name 5 def eat(self): 6 print('%s is eating' % self.name) 7 def shit(self): 8 print('%s is shitting' % self.name) 9 def pee(self): 10 print('%s is peing' % self.name) 11 12 class Cat(Animal): 13 def yell(self): 14 print('MiaoMiao') 15 def eat(self): #继承eat方法然后重构 16 print('%s is eatting fish' % self.name) 17 owner = 'Tom'): #继承owner属性然后重构 18 19 class Dog(Animal): 20 def yell(self): 21 print('WangWang') 22 def eat(self): #继承eat方法然后重构 23 print('%s is eatting bone' % self.name) 24 owner = 'Jack' #继承owner属性然后重构 25 26 cat1 = Cat('Baimao') 27 dog1 = Dog('XiaoHuang') 28 cat1.eat() 29 dog1.eat() 30 print(cat1.owner) 31 print(dog1.owner) 32 33 运行结果: 34 Baimao is eatting fish 35 XiaoHuang is eatting bone 36 Tom 37 Jack
这里还有一个问题,多继承呢?是否可以继承多个类?
如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
经典类多继承栗子:
1 class D: 2 3 def bar(self): 4 print 'D.bar' 5 6 7 class C(D): 8 9 def bar(self): 10 print 'C.bar' 11 12 13 class B(D): 14 15 def bar(self): 16 print 'B.bar' 17 18 19 class A(B, C): 20 21 def bar(self): 22 print 'A.bar' 23 24 a = A() 25 # 执行bar方法时 26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 27 # 所以,查找顺序:A --> B --> D --> C 28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 29 a.bar()
新式类多继承栗子:
1 class D(object): 2 3 def bar(self): 4 print 'D.bar' 5 6 7 class C(D): 8 9 def bar(self): 10 print 'C.bar' 11 12 13 class B(D): 14 15 def bar(self): 16 print 'B.bar' 17 18 19 class A(B, C): 20 21 def bar(self): 22 print 'A.bar' 23 24 a = A() 25 # 执行bar方法时 26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 27 # 所以,查找顺序:A --> B --> C --> D 28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 29 a.bar()
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
总结:经典类深度优先,新式类广度优先
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
python3 中经典类和新式类都遵循广度优先原则,2.x中的深度优先已经被废弃
3.3 多态
目前对于多态的理解很不到位,查了一些资料,很多地方是轻猫淡写,先暂时引用一段到目前为止觉得讲述不错的博客把,后续有深入理解了再更新补充。原出处http://www.cnblogs.com/luchuangao/p/6739557.html
很多人喜欢将多态与多态性二者混为一谈,然后百思不得其解,其实只要分开看,就会很明朗。
(1)多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
- 序列类型有多种形态:字符串,列表,元组
- 动物有多种形态:人,狗,猪
1 #多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) 2 class Animal: 3 def run(self): 4 raise AttributeError('子类必须实现这个方法') 5 6 7 class People(Animal): 8 def run(self): 9 print('人正在走') 10 11 class Pig(Animal): 12 def run(self): 13 print('pig is walking') 14 15 16 class Dog(Animal): 17 def run(self): 18 print('dog is running') 19 20 peo1=People() 21 pig1=Pig() 22 d1=Dog() 23 24 peo1.run() 25 pig1.run() 26 d1.run()
(2)多态性
- 什么是多态性
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
1 #多态性:一种调用方式,不同的执行效果(多态性) 2 def func(obj): 3 obj.run() 4 5 func(peo1) 6 func(pig1) 7 func(d1) 8 9 10 # peo1.run() 11 # pig1.run() 12 13 14 # 多态性依赖于:继承 15 ##多态性:定义统一的接口, 16 def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值 17 obj.run() #调用的逻辑都一样,执行的结果却不一样 18 19 func(peo1) 20 func(pig1) 21 22 func(d1)
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
综上可以说,多态性是一个接口(函数func),多种实现(如people.talk())
- 多态性的好处
其实大家从上面多态性的例子可以看出,我们并没有增加上面新的知识,也就是说Python本身就是支持多态性的,这么做的好处是什么呢?
(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 '''
总结:
多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) 多态性:一种调用方式,不同的执行效果(多态性)
写在最后:
初学面向对象,还有很多基础概念和重要知识待续,因篇幅所限,另外开一篇博客继续进阶把~