第十章 初识面向对象
1.面向对象思想的引入
例:公司需要开发一款人狗大战的游戏
需要两个角色:人 狗
人的属性:昵称、性别、生命值、战斗力、背包
狗的属性:昵称、品种、生命值、战斗力
再尚未学习面向对象知识的时候,我们只是知道需要两个字典描述这两个角色
例1:初步思想
# 例1:初步思想 # 人 person = {'name':'alex', 'sex':'不详', 'hp':250, 'dps':5, 'bag':[]} # 狗 dog = {'name':'2哈', 'kind':'哈士奇', 'hp':15000, 'dps':200} # 说明: 这样虽然已经能够描述这两个角色,但是如果增加角色,就需要重新创建字典,很是繁琐
例2:使用函数创建两个模子
# 例2:使用函数创建两个模子 def Person(name, sex, hp, dps): # 人模子 dic = {'name': name, 'sex': sex, 'hp':hp, 'dps': dps, 'bag': []} return dic def Dog(name, kind, hp, dps): # 人模子 dic = {'name': name, 'kind': kind, 'hp':hp, 'dps': dps, 'bag': []} return dic alex = Person('alex', 'N/F', 250, 5) ha2 = Dog('2哈', '哈士奇', 15000, 200) print(alex) # {'name': 'alex', 'sex': 'N/F', 'hp': 250, 'dps': 5, 'bag': []} print(ha2) # {'name': '2哈', 'kind': '哈士奇', 'hp': 15000, 'dps': 200, 'bag': []} # 说明:这样创建人物已经很方便了,接下来就要进行人狗大战了
例3:人狗大战 -- 使用函数
# 例3:人狗大战 def Person(name, sex, hp, dps): # 人模子 dic = {'name': name, 'sex': sex, 'hp':hp, 'dps': dps, 'bag': []} return dic def Dog(name, kind, hp, dps): # 人模子 dic = {'name': name, 'kind': kind, 'hp':hp, 'dps': dps, 'bag': []} return dic alex = Person('alex', 'N/F', 250, 5) ha2 = Dog('2哈', '哈士奇', 15000, 200) # 人打狗 def attack(person_name, dog_name, person_dps, dog_hp): print('%s打了%s,%s掉了%s点血,剩余%s点血' % (person_name, dog_name, dog_name, person_dps, dog_hp-person_dps)) attack(alex['name'], ha2['name'], alex['dps'], ha2['hp']) # alex打了2哈,2哈掉了5点血,剩余14995点血 # 说明人狗大战的时候,需要输入的参数过多,不好调用
例4:简化人狗大战函数
# 例4:简化人狗大战函数 def Person(name, sex, hp, dps): # 人模子 dic = {'name': name, 'sex': sex, 'hp':hp, 'dps': dps, 'bag': []} return dic def Dog(name, kind, hp, dps): # 人模子 dic = {'name': name, 'kind': kind, 'hp':hp, 'dps': dps, 'bag': []} return dic alex = Person('alex', 'N/F', 250, 5) ha2 = Dog('2哈', '哈士奇', 15000, 200) # 人打狗 def attack(person, dog): dog['hp'] -= person['dps'] print('%s打了%s,%s掉了%s点血,剩余%s点血' % (person['name'], dog['name'], dog['name'], person['dps'], dog['hp']-person['dps'])) # 狗咬人 def bite(dog, person): person['hp'] -= dog['dps'] print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (dog['name'], person['name'], person['name'], dog['dps'], person['hp']-person['dps'])) attack(alex, ha2) # alex打了2哈,2哈掉了5点血,剩余14990点血 attack(alex, ha2) # alex打了2哈,2哈掉了5点血,剩余14985点血 bite(ha2, alex) # 2哈咬了alex,alex掉了200点血,剩余45点血 # 异常展示 attack(ha2, alex) # 2哈打了alex,alex掉了200点血,剩余-350点血 # 说明:虽然这已经满足了我们的需求,但是还是出现 狗打人 人咬狗的错误
例5:升级:将打狗变为人的特有功能,将咬人变为狗的特有功能
# 例5:--升级,将打狗变为人的特有功能,将咬人变为狗的特有功能 def Person(name, sex, hp, dps): dic = {'name': name, 'sex': sex, 'hp': hp, 'dps': dps, 'bag': []} def attack(dog): dog['hp'] -= dic['dps'] print('%s打了%s,%s掉了%s点血,剩余%s点血' % (dic['name'], dog['name'], dog['name'], dic['dps'], dog['hp'])) dic['attack'] = attack return dic def Dog(name, kind, hp, dps): dic = {'name': name, 'kind': kind, 'hp': hp, 'dps': dps} def bite(person): person['hp'] -= dic['dps'] print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (dic['name'], person['name'], person['name'], dic['dps'], person['hp'])) dic['bite'] = bite return dic # 创建人物alex,狗ha2 alex = Person('alex', 'N/F', 250, 5) ha2 = Dog('2哈', '哈士奇', 15000, 200) # 人打狗 alex['attack'](ha2) # alex打了2哈,2哈掉了5点血,剩余14995点血 # 狗咬人 ha2['bite'](alex) # 2哈咬了alex,alex掉了200点血,剩余50点血
思路总结:
1. 面向过程思想
创建一个人
创建一个狗
人打狗 -- 函数
狗咬人 -- 函数
2.面向对象思想
造模子 -- 面向对象
规范了一类角色的属性项目、属性的名字、技能的名字
权限 有一些函数 只能是这个角色才能拥有 才能使用
2.初识类的语法
类 :具有相同属性和相同方法(技能、动作)的一类事物 组成一个类
对象 :具体的某一个具有实际属性和具体动作的一个实体
类是抽象的
对象是具体的
类被创造出来 就是模子 是用来描述对象的
类的编写格式
class 类名: 静态属性 = 123 # def 动态属性(self): pass # 就是函数 def 动态属性(self): # 在类中的方法的一个默认的参数,但也只是一个形式参数 # 约定必须叫self print(self) # 说明 # 类中的变量 -- 静态属性 # 类中的函数 -- 动态属性 # 只要是类中的名字 不管是变量还是函数名 都不能在类的外部直接调用 # 只能通过类名来使用它
类的第一个功能:查看属性
# 只要是类中的名字 不管是变量还是函数名 都不能在类的外部直接调用 # 只能通过类名来使用它 # 1- 查看类的属性 print(类名.__dict__) # 类中必要的默认值之外 还记录了程序员在类中定义的所有名字 # {'__module__': '__main__', '静态属性': 123, '动态属性': <function 类名.动态属性 at 0x0000023F9EB91158>, '__dict__': <attribute '__dict__' of '类名' objects>, '__weakref__': <attribute '__weakref__' of '类名' objects>, '__doc__': None} # 查看静态属性 print(类名.静态属性) # 123 # 2- 修改静态属性 类名.静态属性 = 123345 print(类名.静态属性) # 123345 # 3- 增加静态属性 类名.静态属性2 = 2345 print(类名.静态属性2) # 2345 # 4- 删除静态属性 del 类名.静态属性2 print(类名.静态属性2) # AttributeError: type object '类名' has no attribute '静态属性2' # 查看动态属性 # 类名可以查看某个方法,但是一般情况下 我们不直接使用类名来调用方法 print(类名.动态属性) # <function 类名.动态属性 at 0x00000206B5E61158> 类名.动态属性(1) # 1
类的第二个功能:实例化(创造对象)
# 函数名的第一个字母一定是要小写的 # 类名的第一个字母一定要大写的 class Person:pass alex = Person() # 对象 = 类名() # 实例化的过程 print(alex) # <__main__.Person object at 0x0000023BCFE70B00> # __main__ -- 当前文件 print(Person) # <class '__main__.Person'>
给对象添加属性
# 查看alex的属性 print(alex.__dict__) # {} # 给alex添加属性 alex.__dict__['name'] = 'alex' alex.__dict__['sex'] = '不详' alex.__dict__['hp'] = 250 alex.__dict__['dps'] = 5 alex.__dict__['bag'] = [] print(alex.__dict__) # {'name': 'alex', 'sex': '不详', 'hp': 250, 'dps': 5, 'bag': []} # 简化方法 alex.name = 'alex' # 给alex对象添加属性 alex.hp = 250 alex.dps = 5 alex.sex = '不详' alex.bag = [] print(alex.__dict__) # {'name': 'alex', 'hp': 250, 'dps': 5, 'sex': '不详', 'bag': []}
实例化的过程
# 类名()就是实例化 # 在实例化的过程中 发生了很多事情是外部看不到的 # 1.创建了一个对象 # 2.自动调用__init__方法,进行初始化 # 3.这个被创造的对象会被当作实际参数 传到__init__方法中,并且传给第一个参数self # 执行init方法中的内容
初始化和在外部使用对象来调用属性
# 初识化和在外部使用对象来调用属性 class Person: def __init__(self): self.name = 'alex' self.hp = 250 self.dps = 5 self.sex = '不详' self.bag = [] 升级 class Person: def __init__(self,name,hp,dps,sex): self.name = name self.hp = hp self.dps = dps self.sex = sex self.bag = [] alex = Person('alex',250,5,'N/A') # 参数个数必须与__init__参数相对应 print(alex.__dict__) # {'name': 'alex', 'hp': 250, 'dps': 5, 'sex': 'N/A', 'bag': []} print(alex.name) # alex
使用面向对象的方式编写人狗大战代码
class Person: def __init__(self, name, hp, dps, sex): self.name = name self.hp = hp self.dps = dps self.sex = sex self.bag = [] def attack(self,dog): dog.hp -= self.dps print('%s打了%s,%s掉了%s点血,剩余%s点血' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog: def __init__(self,name,kind,hp,dps): self.name = name self.hp = hp self.kind = kind self.dps = dps def bite(self, person): person.hp -= self.dps print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (self.name, person.name, person.name, self.dps, person.hp)) alex = Person('alex', 250, 5, 'N/A') ha2 = Dog('2哈', '哈士奇', 15000, 200) # Person.attack(alex) # 简化的方式 # alex.attack(ha2) #Person.attack(alex) alex.attack(ha2) alex.attack(ha2) alex.attack(ha2) # alex打了2哈,2哈掉了5点血,剩余14995点血 # alex打了2哈,2哈掉了5点血,剩余14990点血 # alex打了2哈,2哈掉了5点血,剩余14985点血 ha2.bite(alex) ha2.bite(alex) ha2.bite(alex) # 2哈咬了alex,alex掉了200点血,剩余50点血 # 2哈咬了alex,alex掉了200点血,剩余-150点血 # 2哈咬了alex,alex掉了200点血,剩余-350点血 # 说明:对象名.方法名 相当于调用一个函数,这个函数默认把对象名作为第一个参数传入函数 # 剩余的其他参数根据我的需求可以随意传
例:使用面向对象的思想编写已知半径,计算圆的面积和周长
from cmath import pi class Circle: def __init__(self, radius): self.radius = radius def area(self): return pi * self.radius ** 2 def perimeter(self): return 2 * pi * self.radius a = Circle(5) print(a.area()) # 78.53981633974483 print(a.perimeter()) # 31.41592653589793
# 程序运行过程:自上至下一次执行 from cmath import pi # 1- 导入模块 class Circle: # 2- 创建类Circle,在内存中开辟一块内存空间,记录类中的名字__init__,area,perimet # 3- 加载__init__ def __init__(self, radius): # 7- 创建对象,在内存中开辟一块内存空间(self) radius=5 self.radius = radius # 4- 加载area def area(self): return pi * self.radius ** 2 # 5- 加载perimeter def perimeter(self): return 2 * pi * self.radius # 6- 实例化 c1 = Circle(5) # 8- 赋值 # c1.area() ==> Circle.area(c1) print(c1.area()) # 78.53981633974483 # c1.perimeter() ==> Circle.perimeter(c1) print(c1.perimeter()) # 31.41592653589793
面向对象的好处:
每一个角色都有属于自己的属性和方法
高可扩展性: 可以方便添加属性和方法
可读性/规范性
不好的:
结局不可控性
3.类的命名空间
类有自己的命名空间
对象也有自己的命名空间
对象能不能访问类的命名空间? --- 可以
类能不能访问对象的命名空间? --- 不可以
# 运行过程分析 class Person: # 1- 定义类Person COUNTRY = '中国人' # 2- 定义变量 def __init__(self, name): # 3- 定义__init__ self.name = name # 5- 8- 执行__init__ # 4- 实例化alex ==> Person('alex') # 6- 赋值 alex = Person('alex') # 7- 实例化egon ==> Person('egon') # 9- 赋值 egon = Person('egon') print(alex.name) # alex print(egon.name) # egon class Person: COUNTRY = '中国人' def __init__(self, name): self.name = name def eat(self): print('%s在吃面包' % self.name) alex = Person('alex') egon = Person('egon') alex.eat() # Person.eat(alex) # alex在吃面包 print(Person.name) # AttributeError: type object 'Person' has no attribute 'name' # 当一个类在创建一个实例的时候 就产生了一个这个实例和类之间的联系 # 可以通过实例 对象找到实例化它的类 # 但是 类不能找到它的实例化
测试:
# 例1: 类的命名空间分析 class Person: COUNTRY = '中国人' def __init__(self, name): self.name = name def eat(self): print('%s在吃面包' % self.name) alex = Person('alex') egon = Person('egon') print(alex.COUNTRY) # 中国人 alex.COUNTRY == '印度人' print(alex.COUNTRY) print(egon.COUNTRY) print(Person.COUNTRY) # 中国人 # 中国人 # 中国人 # 例2: 类的命名空间分析 class Person: COUNTRY = ['中国人'] def __init__(self, name): self.name = name def eat(self): print('%s在吃面包' % self.name) alex = Person('alex') egon = Person('egon') print(alex.COUNTRY) # ['中国人'] alex.COUNTRY[0] = '印度人' print(alex.COUNTRY) print(egon.COUNTRY) print(Person.COUNTRY) # ['印度人'] # ['印度人'] # ['印度人'] # 说明 # 在访问变量的时候,都显示用自己命名空间中的,如果自己的空间中没有,再到类的空间中去找 # 在使用对象修改该静态变量的过程中,相当于在自己的空间中创建了一个新的变量 # 在类的静态变量(大家都需要的变量)的操作中 应该使用类名来直接进行操作(不应该用对象名修改) 就不会出现乌龙问题
4.组合
组合 一个类的对象作为另一个类对象的属性
# 例1: 添加武器类 class Person: def __init__(self, name, hp, dps, sex): self.name = name self.hp = hp self.dps = dps self.sex = sex self.bag = [] def attack(self,dog): dog.hp -= self.dps print('%s打了%s,%s掉了%s点血,剩余%s点血' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog: def __init__(self,name,kind,hp,dps): self.name = name self.hp = hp self.kind = kind self.dps = dps def bite(self, person): person.hp -= self.dps print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (self.name, person.name, person.name, self.dps, person.hp)) class Weapon: def __init__(self, name, price, dps): self.name = name self.price = price self.dps = dps def kill(self, dog): dog.hp -= self.dps # 给alex装备一个武器 # 肉包子打狗 alex = Person('alex', 250, 5, 'N/A') ha2 = Dog('2哈', '哈士奇', 15000, 200) roubaozi = Weapon('肉包子', 600000, 10000) alex.money = 1000000 if alex.money >= roubaozi.price: alex.weapon = roubaozi alex.weapon.kill(ha2) print(ha2.__dict__) # {'name': '2哈', 'hp': 5000, 'kind': '哈士奇', 'dps': 200}
# 例2: 常见使用实例 # 基础数据类型 都是类 # 'alex' : str的对象 print(alex.name) # => 'alex' # 组合 print(alex.name.startswith('a')) # True
# 例3: 圆形类 -- 圆环类 # 组合 求圆环的面积和周长 from cmath import pi class Circle: def __init__(self,r): self.r = r def area(self): return pi * self.r ** 2 def primeter(self): return 2 * pi * self.r class Annulus: def __init__(self, outr, inr): self.outr = outr self.inr = inr def area(self): return Circle(self.outr).area() - Circle(self.inr).area() def primeter(self): return Circle(self.outr).primeter() + Circle(self.inr).primeter() a = Annulus(5, 3) print(a.area()) print(a.primeter())
总结 : 组合是描述了一种什么有什么的关系 圆环有圆 人有武器
5.继承
为什么会出现继承?
class Person: def __init__(self, name, hp, dps, sex): self.name = name self.hp = hp self.dps = dps self.sex = sex self.bag = [] def attack(self,dog): dog.hp -= self.dps print('%s打了%s,%s掉了%s点血,剩余%s点血' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog: def __init__(self,name,kind,hp,dps): self.name = name self.hp = hp self.kind = kind self.dps = dps def bite(self, person): person.hp -= self.dps print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (self.name, person.name, person.name, self.dps, person.hp))
仔细观察上面的代码,你会发现在定义Person/Dog类的时候都创建了name/hp/dps属性,这样就出现了多个类的冗余,所以就引入了继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类成为派生类或子类
继承的分类
# 单继承 class Parent:pass class Son(Parent):pass print(Son.__bases__) # 查看父类 # (<class '__main__.Parent'>,) # Son类 继承 Parent类 # 父类 基类 超类 -- Parent类 # 子类 派生类 -- Son类 # 多继承 class Parent1:pass class Parent2:pass class Son(Parent1,Parent2): pass print(Son.__bases__) # 查看父类 # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
含有继承关系的类的实例化过程
class Animal: def __init__(self, name, hp, dps): print('---->') self.name = name self.hp = hp self.dps = dps class Person(Animal):pass class Dog(Animal):pass alex = Person('alex', 250, 5) # ----> ha2 = Dog('哈士奇', 15000, 200) # ----> print(alex.__dict__) # {'name': 'alex', 'hp': 250, 'dps': 5} print(ha2.__dict__) # {'name': '哈士奇', 'hp': 15000, 'dps': 200} # 每当实例化发生的时候发生 # 1. 创建了一个对象 ---> 该对象由子类创建 # 2. 把我创建的对象传给__init__ # 3. 把子类创建的对象传给父类__init__的self # 4. 执行__init__ # 5. 把self返回给alex
类的内存空间的查找顺序
先找对象的内存空间 -> 再找创建这个对象的类的内存空间 -> 父类的
class Animal: def __init__(self, name, hp, dps): print('---->') self.name = name self.hp = hp self.dps = dps class Person(Animal): def __init__(self, name, hp, dps): print('*****') alex = Person('alex', 250, 5) # ***** # 说明: 对象在使用名字的时候,先在子类Person中已经找,如果找到就不到父类Animal中找了
派生属性: 子类添加自己的新的属性或者在自己这里重新定义这些属性(不会影响到父类), 但是需要注意,一旦重新定义了自己的属性且父类重名,那么调用新增的属性时,就以自己为准了。
# 派生属性 class Animal: def __init__(self, name, hp, dps): print('---->') self.name = name self.hp = hp self.dps = dps class Person(Animal): def __init__(self, name, hp, dps, sex): # Animal.__init__(self, name, hp, dps) super().__init__(name, hp ,dps) #super(Person, self).__init__(name, hp ,dps) self.sex = sex # 派生属性 class Dog(Animal): def __init__(self, name, hp, dps, kind): Animal.__init__(self, name, hp, dps) super().__init__(name, hp ,dps) self.kind = kind # 派生属性 alex = Person('alex', 250, 5, 'N/A') ha2 = Dog('2哈', 15000, 200, '哈士奇') print(alex.__dict__) # {'name': 'alex', 'hp': 250, 'dps': 5, 'sex': 'N/A'} print(ha2.__dict__) # {'name': '哈士奇', 'hp': 15000, 'dps': 200}
子类重复的方法 重复的属性都放到父类中,子类特有的方法放到子类中
# 例 class Animal: def __init__(self, name, hp, dps): print('---->') self.name = name self.hp = hp self.dps = dps def eat(self): print('%s吃药回血了' % self.name) class Person(Animal): def __init__(self, name, hp, dps, sex): # Animal.__init__(self, name, hp, dps) super().__init__(name, hp ,dps) #super(Person, self).__init__(name, hp ,dps) self.sex = sex # 派生属性 def attack(self,dog): dog.hp -= self.dps print('%s打了%s,%s掉了%s点血,剩余%s点血' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog(Animal): def __init__(self, name, hp, dps, kind): Animal.__init__(self, name, hp, dps) self.kind = kind # 派生属性 def bite(self, person): person.hp -= self.dps print('%s咬了%s,%s掉了%s点血,剩余%s点血' % (self.name, person.name, person.name, self.dps, person.hp)) alex = Person('alex', 250, 5, 'N/A') ha2 = Dog('哈士奇', 15000, 200, 'zangao') print(alex.__dict__) # {'name': 'alex', 'hp': 250, 'dps': 5, 'sex': 'N/A'} print(ha2.__dict__) # {'name': '哈士奇', 'hp': 15000, 'dps': 200} alex.eat() # alex吃药回血了 ha2.eat() # 哈士奇吃药回血了 alex.attack(ha2) # alex打了哈士奇,哈士奇掉了5点血,剩余14995点血 ha2.bite(alex) # 哈士奇咬了alex,alex掉了200点血,剩余50点血
测试题
class Foo: def __init__(self): self.func() def func(self): print('in Foo') class Son(Foo): def func(self): print('in Son') Son() # in Son # Son() 实例化,创建对象self # 执行父类Foo的init # 调用self.func方法,按照--'先找对象的内存空间 -> 创建这个对象的类的内存空间 -> 父类的'的原则 # 所以,先找Son中的func,输出 in Son
6.多继承
python中有两种类
经典类 py3已经灭绝了, 在py2里还存在,在py2中只要程序员不主动继承object,这个类就是经典类 -- 深度优先算法
新式类 py3所有的类都是新式类,所有的新式类都继承自object -- 在多继承中遵循广度优先算法
python3 class A: def f(self): print('in A') class B(A): def f(self): print('in B') class C(A): def f(self): print('in C') class D(B): def f(self): print('in D') class E(C): def f(self): print('in E') class F(D,E): def f(self): print('in F') print(F.mro()) # 查看继承顺序,遵循广度优先原则, 如图 # [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
super和找父类这件事是两回事
在单继承中 super就是找父类
在多继承中 super的轨迹是根据整个模型的起始点而展开的一个广度优先顺序 遵循mro规则
# 例: class A: def f(self): print('in A') class B(A): def f(self): print('in B') super().f() class C(A): def f(self): print('in C') super().f() class D(B,C): def f(self): print('in D') super().f() d = D() d.f() # in D # in B # in C # in A
继承的总结:
继承的作用
减少代码的重用
提高代码的可读性
规范编码模式
名词说明
抽象: 抽象即抽取类似或者说比较像的部分, 是一个从具体到抽象的过程
继承: 子类继承了父类的方法和属性
派生: 子类在父类方法和属性的基础上产生了新的方法和属性
钻石继承
新式类: 广度优先
经典类: 深度优先
7.多态
在python中处处都是多态
在java(强数据类型语言)定义函数/执行函数参数都要表明参数的数据类型
# java 强数据类型语言 # def func(int a, str b, dic c):pass # func(1, 'x', {'a':'b'})
在python(弱数据类型语言)中,定义函数/执行函数是参数不要求数据类型就可以执行
class Person():pass alex = Person() # 每个对象对应的数据类型,就是对象对应的类 print(type(alex)) # 类型是Person # <class '__main__.Person'> print(type('123')) # <class 'str'> print(type(123)) # <class 'int'>