内容概要
- 面向对象和面向过程
- 面向对象三大特征
- 面向对象的成员
- 类与类之间的关系
- 约束
- type、issubclass、isinstance
- self、super、MRO
1. 面向对象和面向过程
01. 面向过程: 一切以事物的流程为核心. 核心是"过程"二字, 过程是指解决问题的步骤,即, 先先什么, 后干什么. 基于该思想编写程序就好比在编写一套流水线. 是一种机械式的编程思维
优点: 负责的问题流程化, 编写相对简单
缺点: 可扩展性差
02. 面向对象: 程序员站在上帝的角度考虑问题. 一切以对象为中心进行编码
什么是对象? 不好解释. 先解释解释什么是车? 有轱辘, 有方向盘, 有发动机, 会跑的是车. 好. 在解释一个. 什么是人. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是人. 我们给这两个东西下了一个简单的定义. 总结: 具有相同属性和动作的结合体叫对象. 面向对象思维, 要自己建立对象. 自己建立场景.
优点: 可扩展性强
缺点: 编程的复杂度高于面向过程
举个栗子: 要把大象装冰箱
面向过程:
要把大象装冰箱总共分三步
1. 打开冰箱门
2. 壮大想
3. 关门
面向对象:(以对象为中心)
创建大象.(大象是对象,以大象为中心)
让大象进冰箱
创建冰箱(冰箱是对象,以冰箱为中心)
让冰箱去装大象
03. 如何编写面向对象的程序
说了这么多. 面向对象的程序如何编写呢? 想想在我们的世界中. 我们如何造一辆车?先由设计师来设计图纸. 设计师在图纸上勾勒出车应该是xx样的. 应该有什么. 以及这台车的功能等等. 然后交给工厂进行制造. 根据设计师设计的图纸去创造车. 程序也一样. 我们需要先设计一个图纸. 在图纸上把我要创建的对象进行描述. 然后交给工人去创建对象.
在这里, 我们画图纸的过程需要我们写类, 我们用类来描述一个对象. 类的语法很简单.
class 类名:
pass
创建一个车类:
class Car:
pass
这就创建了一个类. 图纸有了. 怎么创建一辆车呢? 也很简单. 我们把图纸交给工人帮我们创建一个车的实例. 这个过程被称为实例化. 实例化只需要: "类名()"就可以了
c1 = Car() # 创建一辆车(实例化对象)
车有了. 车至少得有个颜色, 车牌, 排量等等信息啊. 不同的车, 有不同的颜色, 车牌, 排量等.
# 使用"对象.特征"可以给对象设置属性信息
c1.color = "天蓝色"
c1.pai = "京A88888"
c1.pailiang = "1.6T" # 查看车的属性
print(c1.color)
print(c1.pai)
print(c1.pailiang)
接下来, 再造一辆车, 并给车设置相关的属性信息
c2 = Car()
c2.color = "粉色"
c2.pai = "京A66666"
c2.pailiang = "2.T" print(c1.color) # 执行结果: 天蓝色
print(c2.color) # 执行结果: 粉色
# 内存地址不一样,是两个独立的对象
print(c1) # <__main__.Car object at 0x00000000023FF780>
print(c2) # <__main__.Car object at 0x00000000023FF7B8>
我们发现, 这两辆车是完全不同的两辆车. 但是. 拥有相同的属性和信息. 是不是有点冗余了? 怎么办呢? 想想. 我们把车的信息如果写在类里是不是会更好呢? 而且. 我的车在创建的时候这些信息应该已经是设计好了的. 不应该是后天设计的. 好了, 我们知道需求了, 在创建对象的时候能给对象设置一些初始化的属性信息。
在python中我们可以用__init__(self)函数给对象进行初始化操作. 这个函数(方法)被称为构造函数(方法).
class Car:
def __init__(self,color,pai,pailiang): # self表示当前类的对象. 当前你创建的是谁, 谁来访问的这个方法.那这个self就是谁.
self.color = color
self.pai = pai
self.pailiang = pailiang c1 = Car("绿色","京A88888","1.6T")
c2 = Car("粉色","京A66666","2.0T") # 和上面一样,这两个对象依然可以完成属性的设置.
print(c1.color)
print(c2.color)
# 绿色
# 粉色
# 调用类时,会自动执行__init__ 方法
class lei:
def __init__(self):
print("self :",self)
print("进行初始化,哈哈哈") lei()
属性设置完了. 接下来. 车不光有这些信息啊. 车还会跑呢. 跑是一个动作. 所以我们要把跑写成一个函数. 但是在面向对象编程中. 我们不应该叫函数了, 改成叫方法. 只不过这个方法写起来比正常的方法多一个参数self. 仅此而已
class Car:
def __init__(self,color,pai,pailiang):
self.color = color
self.pai = pai
self.pailiang = pailiang def run(self,speed):
print("车跑了 %s迈,牌照是:%s" %(speed,self.pai)) c = Car("黑色","京A88888","3.0T") # 调用c的run方法,c 可以调用那些方法,取决于Car类中定义了那些方法
c.run("")
#执行结果: 车跑了 300迈,牌照是:京A88888
总结:
面向对象的编程方案:
1. 先构思
2. 写类
3. 创建对象
4. 让对象去执行方法
类与对象的关系:
类是对事物的总结. 抽象的概念. 类用来描述对象.
对象是类实例化的结果. 什么都是对象. python里万事万物皆为对象.
对象能执行哪些方法. 都由类来决定. 类中定义了什么. 对象就拥有什么
04. 面向对象和面向过程的PK
那么面向对象和面向过程到底哪个好? 具体问题. 具体分析. 没有绝对的好和不好.来看下面两个小案例
1. 大象装冰箱
# 面向过程
# 非函数版
print("打开冰箱门")
print("装大象")
print("关冰箱门") # 函数版
def open_door():
print("打开冰箱门") def zhuang():
print("装大象") def close_door():
print("关冰箱门") open_door()
zhuang()
close_door()
# 面向对象
class Daxiang:
def open(self):
print("打开冰箱门") def zhuang(self):
print("把自己装进去") def close(self):
print("关冰箱门") dx = Daxiang()
dx.open()
dx.zhuang()
dx.close()
面向过程(胜利):代码简单. 维护相对麻烦(代码量十万级时)
面向对象:代码比较多. 思考起来比较麻烦. 可维护性比较高. 名称空间是独立的
2. 佩奇大战哥斯拉, 佩奇大战奥特曼, 佩奇大战潘金莲
# 面向过程
def dazhan_gesila(name, jineng, attack):
print("%s 使用了 %s技能 打掉了哥斯拉 %s 血量" % (name, jineng, attack)) def dazhan_aoteman(name, jineng, attack):
print("%s 使用了 %s技能 打掉了奥特曼 %s 血量" % (name, jineng, attack)) def dazhan_panpan(name, jineng, attack):
print("%s 使用了 %s技能 打掉了小潘潘 %s 血量" % (name, jineng, attack)) dazhan_gesila("小猪佩奇", "嘴巴嘟嘟", )
dazhan_aoteman("小猪佩奇", "嘴巴嘟嘟", )
dazhan_panpan("小猪佩奇", "嘴巴嘟嘟", )
# 面向对象
class player:
def __init__(self,name,jineng,attack):
self.name = name
self.jineng = jineng
self.attack = attack def dazhan_gesila(self):
print("%s 使用了 %s技能 打掉了哥斯拉 %s 血量" % (self.name, self.jineng,self.attack)) def dazhan_aoteman(self):
print("%s 使用了 %s技能 打掉了奥特曼 %s 血量" % (self.name, self.jineng,self.attack)) def dazhan_panpan(self):
print("%s 使用了 %s技能 打掉了小潘潘 %s 血量" % (self.name, self.jineng,self.attack)) # 优点:对数据进行了封装, 直接用对象.数据就可以了. 让调用变的简单
# 缺点:代码量比较大. 思维方式比较麻烦
peiqi = player("小猪佩奇", "嘴巴嘟嘟", )
peiqi.dazhan_aoteman()
peiqi.dazhan_aoteman()
peiqi.dazhan_panpan()
结论:
面向过程更适合一些小的应用 (脚本)
面向对象更适合大型或者超大型应用
2. 面向对象三大特征
面向对象三大特征: 封装, 继承, 多态. 只要是面向对象编程语言. 都有这三个特征。
01. 封装
在class中:对属性的封装、对方法的封装
class Car:
# 对属性的封装
def __init__(self,color,pai,owner):
self.color = color
self.pai = pai
self.owner = owner # 对方法的封装
def run(self):
pass def jump(self):
pass c = Car("绿色","京A12345","周星星")
c.run()
c.jump()
把很多数据封装到一个对象中. 把固定功能的代码封装到一个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了一个很牛B的函数. 那这个也可以被称为封装. 在面向对象思想中. 是把一些看似无关紧要的内容组合到一起统一进行存储和使用. 这就是封装.
02. 继承
子类可以自动拥有父类中除了私有内容外的其他所有内容,当出现x是一种y的时候. 建议使用继承关系。比如:猫是一种动物,猫继承动物的一些属性。
继承的语法:在写类的时候多写个括号.
# 父类
class Animai:
def run(self):
print("我是动物")
# 子类,继承Animai类
class Cat(Animai):
pass c = Cat() # 子类对象
c.run() # class Cat 中没有定义 run 的方法。调用的是父类中的 run
# 多继承
class Fu1:
def money(self):
print("有钱") class Fu2:
def play(self):
print("玩儿") class Zi(Fu1,Fu2):
pass z = Zi() # 子类对象 # 子类可以拥有两个父类的所有方法
z.money()
z.play()
# 当继承的两个父类出现相同的方法
class Fu1:
def money(self):
print("有钱1") class Fu2:
def money(self):
print("有钱2") class Zi(Fu1,Fu2):
pass z = Zi() # 子类对象
z.money()
# 执行结果: 有钱1 方法查找顺序: 自己 --> Fu1 -->Fu2
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
方法的查找顺序(MRO): python3中使用的是C3算法merge. 下面介绍
03. 多态
python所有的内容都是多态,同一个对象,多种形态。比如. 我们创建一个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "alex", 这时, a又变成了字符串类型. 这个就是多态性. 同一个变量a可以是多种形态.
接下来. 我们来看一个程序.
class Baigu:
def aizou(self):
print("白骨精疼") class HeiXiong:
def aizou(self):
print("黑熊疼") class Car:
def aizou(self):
print("我的车挨揍了") def da(yaoguai): # 我不知道妖怪具体是什么.
yaoguai.aizou() # 我的要求是妖怪必须会挨揍
# 只要会挨揍. 我就可以认为它是妖怪 c = Car()
da(c)
多态的好处: 程序具有超高的可扩展性. 面向对象思想的核心与灵魂. python自带多态。
python的多态性是鸭子类型 :鸭子类型_百度百科
3. 面向对象的成员
什么是类的成员. 很简单. 你能在类中写什么? 写的内容就是成员。类里可以写变量、方法(函数)、属性、私有方法、特殊方法。
01. 变量
在类中变量分为两大类:实例变量和类变量
# 实例变量
class Car:
def __init__(self,color,pai,owner):
# 实例变量,表示创建的每一个对象都有这三个变量
self.color = color
self.pai = pai
self.owner = owner # 实例变量:属于对象的变量
c = Car("蓝色","京A12345","周星星")
内存执行流程:
# 类变量
class Person:
# 类变量, 表示该类创建的所有对象都共享这个变量
country = "中国" def __init__(self,name,age):
self.name = name
self.age = age p = Person("佩奇",) # 此时都是实例变量 # 类变量可以通过类名访问,也可以通过对象访问
print(p.country)
print(Person.country)
为什么可以通过对象访问 ? 看图:
# 给 p 对象添加一个属性. country
class Person: country = "中国" # 类变量 def __init__(self,name,age):
self.name = name
self.age = age p = Person("佩奇",) # 此时都是实例变量 p.country = "大清"
print(Person.country)
print(p.country) # 只是在自己的内存空间中 创建了一个变量 country = "大清"
# 执行结果:
# 中国
# 大清
# 案例. 通过程序来记录当前类被创建了多少个对象.
class Foo:
count = def __init__(self):
Foo.count += print(Foo.count)
f1 = Foo()
f2 = Foo()
f3 = Foo()
print(Foo.count) # 执行结果:
#
#
简单的总结:
实例变量, 给对象用的.
类变量, 多个对象共享的. 最好是用类名来访问. 这样更加规范
02. 方法
在类中方法分为三大类: 实例(成员)方法、类方法、静态方法。
# 先说第一个成员方法. 用的最多的就是这种. 就是对象直接访问的方法叫成员方法。
class Car:
# 实例方法(成员方法)
def run(self):
print("车能跑") c = Car()
c.run() # 从属于对象的,必须对象访问
# 类方法,属于类的. 一般使用类名去访问. 对象也可以访问
类方法第一个参数是cls. 类方法在被调用的时候也不需要传递实例对象.系统会自动的把类传递给第一个参数. 类方法在编写的时候, 需要在类方法上面添加 @classmethod 装饰器
class Computer:
def play(self):
print("我的电脑可以玩儿") @classmethod # 加 @classmethod 就是类方法
def cal(cls,a,b):
print(cls)
return a + b print(Computer.cal(,)) # 此时会自动把类名传递给类方法的第一个参数:cls # 执行结果:
<class '__main__.Computer'>
3
# 静态方法,就是在类中写函数
静态方法. 静态方法不需要我们给方法传递self. 也就是说. 当出现一个方法不需要使用到成员变量的时候. 就可以选择使用静态方法. 静态方法需要我们在方法上面添加一个@staticmethod
class Car:
@staticmethod
def jing():
print("我是静态方法,我不需要参数")
# 一般使用类名直接访问和调用
Car.jing() # 类名可以直接访问 # 对象也可以访问,最好不要这样干,以便于区分静态方法和实例方法
c = Car()
c.jing()
03. 属性
属性其实就是通过方法改造过来的一种变量的写法, 在方法上添加一个@property
应用场景: 年龄一般都保存的出生年月日. 然后由程序来计算当前的年龄. 实时的. 那这个时候就需要进行相应的计算了. 而计算属于一个功能. 当然要写方法里了. 但是对于年龄这个属性而言. 他应该是一个数值. 而不是动作. 所以python就提供了这样一种机制. 通过方法来描述一个属性
class Person:
def __init__(self,name,birthday,gender):
#对象属性
self.name = name
self.birthady = birthday
self.gender = gender # 把方法变成属性值
@property # age = 现在的时间 - 出生年月日 即: - self.birthady
def age(self):
return - self.birthady # 粗略的写一下 p = Person("周星星",,"男")
print(p.name)
print(p.age) # 没有加括号调用,像查看上面属性一样,直接用
#执行结果:
注意:
1. 方法参数只能有一个self
2. 方法上面要写@property
3. 调用的时候, 我们不需要写括号. 直接当成属性变量来用就可以了.
4. 这种套路只能取值. 不能设置值
04. 私有
在python中使用__作为方法或者变量的前缀. 那么这个方法或者变量就是一个私有的。私有的内容. 子类是无法继承的.
# 私有变量
class Person:
__aihao = "洗澡时唱歌" # 类的私有变量
def __init__(self,laopo,mimi):
self.__laopo = laopo # 实例的私有变量
self.__mimi = mimi p = Person("李坦克","自私的")
print(p.__mimi)
程序报错,私有的内容是访问不到的
AttributeError: 'Person' object has no attribute '__mimi'
# 在类的内部可以访问私有内容
class Person:
__aihao = "洗澡时唱歌" # 类的私有变量
def __init__(self,laopo,mimi):
self.__laopo = laopo # 实例的私有变量
self.__mimi = mimi def gaosuni(self):
print("大喇叭开始广播了")
return self.__mimi # 在类的内部可以访问私有内容 p = Person("李坦克","自私的")
ret = p.gaosuni() # 通过一个非私有的方法, 访问到了他的秘密.
print(ret)
私有的内容不能直接访问. 但是如果对外开辟了外界访问的通道(公共方法). 那可以通过这个公共的方法来获取到私有的内容. 这样做的好处是. 外界, 只能看, 但是改不了.
# 私有方法
class Person:
def __init__(self):
pass def __yue(self): # 私有方法
print("我要约会") def job(self):
print("我要工作")
p = Person()
p.yue() #报错: AttributeError: 'Person' object has no attribute 'yue'
p.job()
__yue是一个私有的方法. 只能在类中自己调用. 类外面不能访问. job是一个成员方法. 并且是一个开放的方法. 在类外界可以被访问到同样的. 类中的私有方法也是相对而言的. 我们可以通过其他方法来访问到这样的方法.
class Person:
def __init__(self):
pass def __yue(self):
print("我要约会") def job(self):
print("我要工作")
self.__yue() # 在自己类中访问自己的其他方法. 哪怕是私有的. 也是自己在用
p = Person()
p.job()
# 私有的内容. 子类是无法继承的.
class Fu:
__qingfu = "情妇_小潘潘" class Zi(Fu):
pass print(Zi.__qingfu) # 报错
05. 特殊成员(下节)
4. 类与类之间的关系
01 . 依赖关系
类与类之间的关系比较松散, 俗称解耦,比如,人类要玩游戏,依赖于电脑。但不是某一台电脑,只要是正常的电脑就行。
代码实现:通过参数把另一个类的对象传递进来
class Person:
def play(self,computer): # 依赖Computer类:通过参数把另一个类的对象传进来
print("开始玩")
computer.start_game() # 执行传进来对象的方法。
print("关闭游戏") class Computer:
def __init__(self,name):
self.name = name def start_game(self):
print("使用 %s 电脑, 击杀盖伦" % self.name) # 创建人
p = Person() c1 = Computer("MacBook")
c2 = Computer("外星人") p.play(c1)
p.play(c2)
02. 关联关系,组合关系,聚合关系
关联关系:在逻辑上出现了. 我需要你. 你还得属于我. 这种逻辑 就是关联关系. 这种关系的紧密程度比依赖关系要紧密的多.
组合关系和聚合关系. 代码上的差别不大. 都是把另一个类的对象作为这个类的属性来传递和保存. 只是在含义上会有些许的不同而已.
1对1的关联关系
class Card:
def __init__(self,no,date):
self.no = no
self.date = date class Person:
def __init__(self,card,name,birthdat):
self.card = card # 产生了关联关系:在一个对象的内部添加另一个对象
self.name = name
self.birhday = birthdat c = Card("","")
p = Person(c,"周星星","1994-09-06")
# 打印Card类的date属性
print(p.card.date)
# 打印Card类的no属性
print(p.card.no)
1对多的关联关系
class Ban:
def __init__(self,name,date,stu_lst=[]):
self.name = name
self.date = date
self.stu_lst = stu_lst # 通过这句话和下面的类产生了关系,列表中的元素是Stu类实例化的对象 def display(self):
for s in self.stu_lst: # stu_lst = [s1,s2,s3]
print(s.name) # s.name ==> s1.name s2.name s3.name class Stu:
def __init__(self,name):
self.name = name b = Ban("s25","2018-12-31") s1 = Stu("周杰伦")
s2 = Stu("周星星")
s3 = Stu("渣渣辉") b.stu_lst.append(s1) # 把对象追加到列表中
b.stu_lst.append(s2)
b.stu_lst.append(s3) b.display()
03. 继承关系,实现关系
在面向对象的世界中存在着继承关系. 我们现实中也存在着这样的关系. 我们说过. x是一种y, 那x就可以继承y. 这时理解层面上的. 如果上升到代码层面. 我们可以这样认为. 子类在不影响父类的程序运行的基础上对父类进行的扩充和扩展. 这里.我们可以把父类被称为超类或者基类. 子类被称为派生类.
5. 约束
约束是对类的约束. 比如. 现在. 你是一个项目经理. 然后呢. 你给手下的人分活. 张三, 你处理一下普通用户登录, 李四, 你处理一下会员登录, 王五, 你处理一下管理员登录. 那这个时候呢. 他们就开始分别取写他们的功能了. 但是呢. 你要知道, 程序员不一定会有那么好的默契. 很有可能三个人会写完全三个不同的方法. 就比如这样:
class Normal: # 张三,普通人登录
def login(self):
pass class Member: # 李四,会员登录
def denglu(self):
pass class Admin: # 王五,管理员登录
def login(self):
pass
然后呢, 他们把这样的代码交给你了. 你看了一眼. 张三和王五还算OK 这个李四写的是什么鬼? denglu.......难受不. 但是好歹能用. 还能凑合. 但是这时. 你这边要使用了. 问题就来了.
# 项目经理写的总入口
def login(obj):
print("准备验证码......")
obj.login()
print("进入主页.......") cls_lst = [Normal(),Member(),Admin()] for obj in cls_lst:
login(obj) # 报错: Member 和 Admin 中没有login方法。
对于张三和王五的代码. 没有问题. 但是李四的. 你是不是调用不了. 那如何避免这样的问题呢? 我们要约束程序的结构. 也就是说. 在分配任务之前就应该把功能定义好. 然后分别交给底下的程序员来完成相应的功能.
在python中有两种办法来解决这样的问题:
1. 提取父类. 然后在父类中定义好方法. 在这个方法中什么都不用干. 就抛一个异常就可以了. 这样所有的子类都必须重写这个方法. 否则. 访问的时候就会报错
2. 使用元类来描述父类. 在元类中给出一个抽象方法. 这样子类就不得不给出抽象方法的具体实现. 也可以起到约束的效果.
# 第1种解决方案: 提取父类. 在父类中给出一个方法. 并且在方法中不给出任何代码. 直接抛异常.
class Base:
def login(self): # 调用这个方法就抛出一个异常
# 对子类进行约束,必须重写login方法
raise NotImplementedError("你没有写login方法!!!!") class Normal(Base): # 张三,普通人登录
def login(self):
pass class Member(Base): # 李四,会员登录
def denglu(self):
pass class Admin(Base): # 王五,管理员登录
def login(self):
pass # 项目经理写的总入口
def login(obj):
print("准备验证码......")
obj.login()
print("进入主页.......") cls_lst = [Normal(),Member(),Admin()]
for obj in cls_lst:
login(obj)
# 报错: raise NotImplementedError("你没有写login方法!!!!")
自己没有login方法就会去调用父类中的login方法,但是父类中的方法会抛出一个异常. 所以报错. 这样就必须重写login方法. 从而对子类进行了相应的约束.
# 抽象类和接口类
# 如果类中所有的方法都是抽象方法. 这个类又被成为接口interface
from abc import ABCMeta, abstractmethod class Animal(metaclass=ABCMeta): # 元类. 固定写法. 表示这个类是一个抽象类
@abstractmethod # abstract: 抽象. 一个类中如果有了抽象方法. 这个类一定是抽象类
def chi(self): # 这个吃没办法完美的解释出来. chi是一个抽象的概念 该方法应该是一个抽象方法
pass class Cat(Animal):
def chi(self): # 方法的重写和覆盖
print("猫吃鱼") # 抽象类不可以创建对象
# a = Animal()
# 报错:TypeError: Can't instantiate abstract class Animal with abstract methods chi c = Cat() # Cat 必须重写chi这个方法,否则也不能创建实例
# TypeError: Can't instantiate abstract class Cat with abstract methods chi
c.chi()
总结: 约束. 其实就是父类对子类进行约束. 子类必须要写xxx方法. 在python中约束的方式和方法有两种:
1. 使用抽象类和抽象方法, 由于该方案来源是java和c#. 所以使用频率还是很少的
2. 使用人为抛出异常的方案. 并且尽量抛出的是NotImplementError. 这样比较专业, 而且错误比较明确.(推荐)
6. type、issubclass、isinstance
# type() 精准的返回这个对象的数据类型
class Foo:
pass class Bar(Foo):
pass b = Bar() print(type(b))
# 执行结果: <class '__main__.Bar'>
# isinstance 判断xxx是否是xxx类型的,可以向父类找
print(isinstance(b,Bar))
# True
print(isinstance(b,Foo))
# True a = Foo()
print(isinstance(a,Bar)) # 不能从父类向子类找
# False
# issubclass 判断xxx是否是xxx的子类
print(issubclass(Bar,Foo))
# True
print(issubclass(Foo,Bar))
# False
# 应用
def cul(a, b): # 计算加法
if (type(a) == int or type(a) == float) and (type(b) == int or type(b) == float):
return a + b
else:
return "没法给你计算" # 干死它 print(cul(,"哈哈"))
7. self
01. self
不管方法之间如何进行调用. 类与类之间是何关系. 默认的self都是访问这个方法的对象.
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num) class Foo(Base):
pass obj = Foo()
obj.func1() # 运行的是Base中的func1
执行流程:
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num) class Foo(Base):
def func1(self):
print("Foo. func1", self.num) obj = Foo()
obj.func1() # 执行结果:
# Foo. func1
执行流程:
class Base:
def __init__(self, num):
self.num = num def func1(self):
print(self.num)
self.func2() def func2(self):
print(, self.num) class Foo(Base):
def func2(self):
print(, self.num) lst = [Base, Base, Foo] # 类可以作为列表的元素
for obj in lst:
obj().func1()
结论: self就是你访问方法的那个对象. 先找自己, 然后在找父类的
8. MRO和C3算法
01. python多继承
python支持多继承. 一个类可以拥有多个父类.
class ShenXian: # 神仙
def fei(self):
print("神仙都会飞") class Monkey: # 猴
def chitao(self):
print("猴子喜欢吃桃子") class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是一只猴
pass sxz = SunWukong() # 孙悟空
sxz.chitao() # 会吃桃
sxz.fei() # 会飞
此时, 孙悟空是一只猴子, 同时也是一个神仙. 那孙悟空继承了这两个类. 孙悟空自然就可以执行这两个类中的方法
在多继承中当两个父类中出现了重名方法的时候. 会根据MRO(method resolution order 方法的查找顺序)去查找父类方法。在不同的python版本中使用的是不同的算法来完成MRO的.
在python2中存在两种类:
经典类: (已经不存在了)
class lei: #表示谁都不继承,独立的
pass
新式类,新式类的特点是基类的根是object
class lei(object):
pass python3:都是新式类. 如果基类谁都不继承. 那这个类会默认继承object
02. 经典类的MRO
经典类的查找顺序: 树形结构的深度优先遍历
03. 新式类的MRO
看到的效果: 把顶端继承拿掉. 根据深度优先遍历.得到MRO. 最后计算顶端继承。真正的官方提供的是C3算法. merge(). 从来python都没有使用过广度优先.
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E(C, A):
pass
class F(D, E):
pass
class G(E):
pass
class H(G, F):
pass
类名.__mro__获取到类的MRO信息.
print(H.__mro__)
# (<class '__main__.H'>, <class '__main__.G'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
9. super
super()可以帮我们执形MRO中下一个父类的方法. 通常super()有两个使用的地方:
1. 可以访问父类的构造方法
2. 当子类方法想调用父类(MRO)中的方法
# MRO + super
class Init(object):
def __init__(self, v):
print("init")
self.val = v class Add2(Init):
def __init__(self, val):
print("Add2")
super(Add2, self).__init__(val)
print(self.val)
self.val += class Mult(Init):
def __init__(self, val):
print("Mult")
super(Mult, self).__init__(val)
self.val *= class HaHa(Init):
def __init__(self, val):
print("哈哈")
super(HaHa, self).__init__(val)
self.val /= class Pro(Add2,Mult,HaHa): #
pass class Incr(Pro):
def __init__(self, val):
super(Incr, self).__init__(val)
self.val+= # MRO: Incr Pro Add2 Mult HaHa Init
p = Incr()
print(p.val)
执行流程:
结论: 不管super()写在哪. 在哪儿执行. 一定先找到MRO列表. 根据MRO列表的顺序往下找. 否则一切都是错的