python学习之面向对象(二)
6.2 类的空间角度研究类
6.2.1 添加对象属性
【总结】对象的属性不仅可以在__init__
里面添加,还可以在类的其他方法或者类的外面添加。
class A:
address = '召唤师峡谷'
def __init__(self, name):
self.name = name
def func(self,type):
self.type = type
# 类的外部可以添加
obj = A('盖伦')
obj.age = 20
print(obj.__dict__)
# 在类的内部可以添加
obj.func('战士')
print(obj.__dict__)
6.2.2 添加类的属性
【总结】类的属性添加方式也是比较灵活的,不仅可以在类内部添加,还可以在类的外部添加。
class A:
aaa = 'aaa'
def __init__(self,name):
self.name = name
def func(self):
A.ccc = 'ccc'
# 在类的外部可以添加
A.bbb = 'bbb'
print(A.__dict__)
# 在类的内部可以添加
A.func(1)
print(A.__dict__ )
6.2.3 对象如何查找类的属性
对象在实体化的空间内,有一个指向类地址空间的指针
对象查找顺序:先从对象空间找--> 类的空间找--> 父类空间找.....
类名查找顺序:先从本类空间找--> 父类空间.....
6.3 类与类的关系
python中类与类之间的关系:
依赖关系
组合关系
继承关系
6.3.1 依赖关系
将一个类名或者类的对象传给另一个类的方法中
模拟一个场景,淘金者要进入OnePeace,淘金者要念口号让大门打开,进入进入OnePeace后要念口号,然后门关上。
class Fossicker:
def __init__(self,name):
self.name = name
def ask_open(self,se):
print(f'{self.name}大喊:“芝麻开门”')
re.open_door() #调用SecretPath里的open_door方法
print(f"{self.name}进入了{se.name}里面")
def ask_close(self,se):
print(f'{self.name}又说:“芝麻关门”')
re.closs_door() #调用SecretPath里的closs_door方法
print(f"{self.name}把{se.name}的门关了")
class SecretPath:
def __init__(self,name):
self.name = name
def open_door(self):
print('门开了')
def closs_door(self):
print('门关了')
e1 = Fossicker('阿加')
se1 = SecretPath('OnePeace')
e1.ask_open(se1)
e1.ask_close(se1)
上述场景,Fossicker的这个类中用到了SecretPath的对象,而SecretPath的对象并没有用到其他类的任何方法或是属性,我们可以说Fossicker类依赖SecretPath类,二者构成依赖关系。
6.3.2 组合关系
将一个类的对象封装到另一个类的对象的属性中
情景模拟:相亲
class Boy:
def __init__(self,name):
self.name = name
def meet(self,gril):
if gril.age <= 25:
self.grilfriend = gril
print(f'{self.name}跟{self.grilfriend.name}很是合得来')
gril.boyfriend = self
else:
self.grilfriend = None
print('不合适,没成')
def have_dinner(self):
if self.grilfriend:
print(f"{self.name}请{self.grilfriend.name}吃大餐")
else:
print('算了回家吃泡面吧')
class Gril:
def __init__(self,name,age):
self.name = name
self.age = age
def shopping(self):
print(f"{self.name}和{self.boyfriend.name}一起去购物")
gailun = Boy('盖伦')
nvjing = Gril('凯特琳',24)
gailun.meet(nvjing)
gailun.have_dinner()
nvjing.shopping()
【总结】不管是依赖关系还是组合关系,在类与类之间进行相互调用时,我们希望的是,某些情况下不要把所有的因素写死,而是获取到其他对象的整个空间内的信息,所以在传参时可以,应注意这一点
6.3.3 练习
场景模拟:模拟英雄联盟的游戏人物
- 创建一个 Game_role的类.
- 构造方法中给对象封装name,ad(攻击力),hp(血量).三个属性.
- 创建一个attack方法,此方法是实例化两个对象,互相攻击的功能:
例: 实例化一个对象 盖伦,ad为10, hp为100
实例化另个一个对象 剑豪 ad为20, hp为80
盖伦通过attack方法攻击剑豪,此方法要完成 '谁攻击谁,谁掉了多少血, 还剩多少血'的提示功能
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
def attack(self,p1):
p1.hp = p1.hp - self.ad
print(f'{self.name}攻击{p1.name},{p1.name}掉了{self.ad}血,还剩{p1.hp}血')
gailun = GameRole('盖伦',10,100)
jianhao = GameRole('剑豪',20,80)
gailun.attack(jianhao) #依赖关系的应用
升级:创建一个武器类,让对象借助武器进行攻击
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
class Weapon:
def __init__(self, name, ad):
self.name = name
self.ad = ad
def weapon_attack(self, p1, p2):
# print(self)
p2.hp = p2.hp - self.ad
print(f'{p1.name}利用{self.name}给了{p2.name}一下,{p2.name}还剩{p2.hp}血')
gailun = GameRole('盖伦', 10, 100)
jianhao = GameRole('剑豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('长枪', 40)
great_sword.weapon_attack(gailun, jianhao) #此时进行攻击动作的执行者是great_sword
此时进行攻击动作的执行者是great_sword,而我们希望的是GameRole的对象使用武器进行攻击,下面进行优化
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
def equip_weapon(self,wea): #
self.weapon = wea
class Weapon:
def __init__(self, name, ad):
self.name = name
self.ad = ad
def weapon_attack(self, p1, p2):
p2.hp = p2.hp - self.ad
print(f'{p1.name}利用{self.name}给了{p2.name}一下,{p2.name}还剩{p2.hp}血')
gailun = GameRole('盖伦', 10, 100)
jianhao = GameRole('剑豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('长枪', 40)
gailun.equip_weapon(great_sword) #依赖关系
gailun.weapon.weapon_attack(gailun,jianhao)
6.4 继承
面向对象的三大特性:封装,继承,多态
6.4.1 继承的初识
B类继承A类,B就叫做A的子类,派生类,子类可以调用父类的属性和方法
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
看上面的代码,建立了三个类,出现了大量的重复代码,利用继承对这些代码进行整合,如下:
# 继承
class Animal:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Person(Animal):
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
6.4.2 继承的特性
继承的优点:
- 节省代码
- 增强代码的耦合性
- 代码更加规范
- 重构父类的属性或方法
继承的缺点:
- 降低了代码的灵活性
- 过多的耦合性,使得在父类的常量、变量和方法被修改时,需要考虑子类的修改,甚至导致大段的代码需要重构
6.4.3 单继承
只有一个父类(基类,超类)
子类以及对象可以调用父类的属性方法
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要进食')
class Person(Animal):
pass
# 从类名执行父类的属性
print(Person.__dict__)
print(Person.live)
对象执行父类的一切
实体化对象首先执行__init__
,自己没有就去父类找,根据__init__
函数传入相应的参数
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要进食')
class Person(Animal):
pass
# 从对象执行父类的一切
p1 = Person('盖伦',24,'男')
print(p1.__dict__)
p1.eat()
【注意】子类以及子类的对象只能调用父类的属性和方法,不能进行操作(增删改)。
子类可以重写父类的方法
就是在子类的空间内,创建一个新的与父类函数重名的函数
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要进食')
class Person(Animal):
def eat(self):
print('人要吃饭')
p1 = Person('盖伦',24,'男')
p1.eat()
【注意】如遇子类属性与父类重名的话,先从本身进行执行,父类不执行
如何执行子类方法有执行父类方法
一种是无绑定的调用父类的函数
一种是使用super自动绑顶
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要进食')
class Person(Animal):
# 方法一:
def __init__(self,name,age,sex,type):
Animal.__init__(self,name,age,sex) #调用父类的方法,需要手动传入self
self.type = type
def eat(self):
print('人要吃饭')
p1 = Person('盖伦',24,'男','战士')
print(p1.__dict__)
# 方法二:super方法
class Person(Animal):
def __init__(self,name,age,sex,type):
# super(Person, self).__init__(name,age,sex) #super第一个括号里的内容可以省略,
super().__init__(name,age,sex)
self.type = type
def eat(self):
print('人要吃饭')
p1 = Person('盖伦',24,'男','战士')
print(p1.__dict__)
# super可以继承父类的方法
class Person(Animal):
def __init__(self,name,age,sex,type):
# super(Person, self).__init__(name,age,sex)
super().__init__(name,age,sex) #super第一个括号的内容可以省略
self.type = type
def eat(self):
print('人要吃饭')
super().eat()
p1 = Person('盖伦',24,'男','战士')
p1.eat()
6.4.4 多继承
一个子类继承了多个父类,难点在于继承的顺序。
面向对象:python2.2之前,都是经典类;python2.2-2.7之间,存在两种类型,一种是经典类另一种是新式类;python3只有新式类。
经典类:基类不继承object,查询规则满足深度优先原则
新式类:基类继承object,查询规则满足mro算法,不是单纯的广度优先
mro算法
表头:列表的第一个元素
表尾:表头以外的所有元素
解题过程:
1.先从上到下,列出每个类的线性表达式.
2.列出的线性表达式中如果有多个表达式的话,需要进行化简.
3.最终的子类也使用线性表达式表示,由于有多个父类,也需要进行化简.
线性表达式有两个部分组成:第一个类名称为头部,剩余的部分称为尾部.
先从上到下求出每个类的线性表达式,期间需要化简
G = [GO]
F = F + [GO] = [FGO]
D = D + [GO] = [DGO]
B = B + [FGO] = [BFGO]
C = C + [E] = [CE]
再求最终的类的线性表达式,需要化简
A = [BFGO] + [CE] + [DGO]
AB = [FGO] + [CE] + [DGO]
ABF = [GO] + [CE] + [DGO]
由于第一个表达式的G是第三个表达式的尾部,所以不能抽取,则从第二个表达式的头部开始抽取C
ABFC = [GO] + [E] + [DGO]
再次从第一个表达式的头部开始抽取,依然不能抽取,则从第二个表达式抽取
ABFCE = [GO] + [DGO]
第一个表达式的头部依然不能抽取,从第二个表达式的头部开始抽取
ABFCED = [GO] + [GO]
剩下的部分相同了,就可以直接写
ABFCEDGO # 最终的mro解析顺序
【注意】在实际项目中,查看mro顺序可以使用 类名.mro来获取类的查找顺序
6.5 封装和多态
6.5.1 封装
给对象封装属性或方法的过程
通过对象或者self获取被封装的属性方法
6.5.2 多态
python默认支持多态
python 设置变量时,不需要设置数据类型
6.5.3 鸭子类型
俗话你看起来像鸭子,那么你就是鸭子
两个相互独立的A,B两个类,但内部功能相似,python一般会将类似于A,B里面的相似功能的命名设置成相同。
举例:A,B虽然没有联系,默认使用一种规范,使用起来更方便。像是列表里的index与字符串的index,两个index没有关系,但是使用的时候比较方便。
6.6 super
super(类名,self).function() 执行本类的父类的方法
类名:多继承中,严格按照self的从属类的mro顺序执行,即执行本类名的父类里的方法;单继承中,按照父类的方法执行
class A:
def func(self):
print('in A')
class B(A):
def func(self):
print('in B')
class C(A):
def func(self):
print('in C')
class D(B,C):
def func(self):
print('in D')
super(D,self).func()
t = D()
t.func()
#输出
in D
in B
# 改变一下class D的super参数
class D(B,C):
def func(self):
print('in D')
super(B,self).func()
t = D()
t.func()
输出
in D
in C #按照mro顺序执行下一父类的方法
6.7 类的约束
约束是对类的约束。
场景模拟:设计一个战士和刺客的英雄类型
class Zhanshi:
def type_role(self,name):
self.name = name
print(f"{self.name}是一个战士")
class Cike:
def type_role(self, name):
self.name = name
print(f"{self.name}是一个刺客")
gailun = Zhanshi()
# g.type_role('战士')
jianhao = Cike()
# j.type_role('刺客')
# 设计一个规范化的接口
def type_role(obj,role): #归一化设计
obj.type_role(role)
type_role(gailun,'盖伦')
type_role(jianhao,'剑豪')
当增加一个法师的类型
# 在上述的基础上增加新的英雄属性
class Fashi:
def type_role(self,name):
self.name = name
print(f"{self.name}是一个法师")
moganna = Fashi()
type_role(moganna,'莫甘娜') #调用规范化接口使用
如果我要按照归一化设计的规范继续增加新的英雄类型时,那么怎么让程序设计者知道要使用这一规范呢?
目前有两种方式:1.在父类建立一种约束;2.模拟抽象类的概念,建立一种强制约束
第一种方式
在父类建议约束,主动抛出错误,非强制性
class Hero:
def type_role(self,name): #定义一种规范,子类要定义type_role方法
raise Exception('请定义type_role方法')
class Zhanshi(Hero):
def type_role(self,name):
self.name = name
print(f"{self.name}是一个战士")
class Cike(Hero):
def type_role(self, name):
self.name = name
print(f"{self.name}是一个刺客")
class Fashi(Hero):
def type_ro(self,name): #没有定义type_role而是定义了一个type_ro
self.name = name
print(f"{self.name}是一个法师")
pass
def type_role(obj,role): #归一化设计
obj.type_role(role)
gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()
type_role(moganna,'莫甘娜') #调用接口函数时,主动报错:raise Exception('请定义type_role方法')
第二种方式
类似于抽象类的概念,定义一种强制约束,你只要是我的派生类,那么这个方法必须要有。
from abc import ABCMeta,abstractmethod
class 父类名(metaclass=ABCMeta):
@abstractmethod
def 函数名
from abc import ABCMeta,abstractmethod
class Hero(metaclass=ABCMeta):
@abstractmethod
def type_role(self,name): #定义一种规范,子类要定义type_role方法
pass
class Zhanshi(Hero):
def type_role(self,name):
self.name = name
print(f"{self.name}是一个战士")
class Cike(Hero):
def type_role(self, name):
self.name = name
print(f"{self.name}是一个刺客")
class Fashi(Hero):
def type_ro(self,name):
self.name = name
print(f"{self.name}是一个法师")
pass
gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()
moganna.type_ro('莫甘娜')
# 函数执行前,直接报错TypeError: Can't instantiate abstract class Fashi with abstract methods type_role