Python基础-初识面向对象
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向过程 VS 面向对象
面向过程的程序设计的核心是(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好的一条流水线 ,考虑周全什么时候处理什么东西。
- 优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
- 缺点:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
- 应用场景:一旦完成基本很少改变的场景,著名的例子有Linux内核,git,以及Apache HTTP Server 等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
- 优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
- 缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
- 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
- 类:具有相同特征的一类事物(人、狗、老虎)
- 对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
- 实例化:类——>对象的过程
示例说明:
示例说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:
std1 = { 'name': 'Michael', 'score': 98 } std2 = { 'name': 'Bob', 'score': 81 }
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
def print_score(std): print('%s: %s' % (std['name'], std['score']))
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student
这种数据类型应该被视为一个对象,这个对象拥有name
和score
这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score
消息,让对象自己把自己的数据打印出来。
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()
类相关的知识
申明
def functionName(args): '函数文档字符串' 函数体 ''' class 类名: '类的文档字符串' 类体 ''' #创建一个类 class Data: pass
注意:类名通常是大写开头的单词
属性
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) #引用人的走路方法,注意,这里不是在调用
实例化的过程就是类——>对象的过程
语法:对象名 = 类名(参数)
p1 = Person()
slef
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是长的帅的人都不会这么做。
因为你瞎改别人就不认识
类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
对象相关的知识
- 实例化对象
格式:对象名 = 类名(参数列表) 注意:没有参数,小括号也不能省略
访问对象的属性和方法
- 访问属性
格式:对象名.属性名 赋值:对象名.属性名 = 新值
- 访问方法
格式:对象名.方法名(参数列表)
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 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
# 在终端输出如下信息 ''' 小明,10岁,男,上山去砍柴 小明,10岁,男,开车去东北 小明,10岁,男,最爱大保健 老李,90岁,男,上山去砍柴 老李,90岁,男,开车去东北 老李,90岁,男,最爱大保健 ''' class Person(object): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def print_hooby(self, hobby): print ("%s, %s岁, %s, %s "%(self.name, self.age, self.sex, hobby)) p1 = Person("小明", 10, "男") p1.print_hooby("上山去砍柴") p1.print_hooby("开车去东北") p1.print_hooby("最爱大保健") p2 = Person("老李", 90, "男") p2.print_hooby("上山去砍柴") p2.print_hooby("开车去东北") p2.print_hooby("最爱大保健")
# 计算一个类实例过多少个对象 class Count: count = 0 def __init__(self): Count.count = self.count + 1 a = Count() b = Count() c = Count() d = Count() print(Count.count)
from math import pi class Circle: """ 定义了一个圆形类; 提供计算面积(area)和计算周长(perimeter)的方法 """ def __init__(self, radius): self.radius = radius def area(self): return self.radius ** 2 * pi def perimeter(self): return self.radius * 2 * pi circle = Circle(3) # 实例化一个圆 area1 = circle.area() # 计算圆面积 per1 = circle.perimeter() # 计算圆周长 print(area1, per1) # 打印圆面积和周长
类名称空间与对象的名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的
class Person: name = "小白" def fun1(self): print(self.name) p1 = Person() p2 = Person() print(id(p1.name)) 2436828402064 print(id(Person.name)) 2436828402064
而类的动态属性是绑定到所有对象的
class Person: name = "小白" def fun1(self): print(self.name) p1 = Person() p2 = Person() print(p1.fun1)__main__.Person object at 0x000002375E4A8C18>> print(Person.fun1)
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
查询顺序:
- 对象.属性:先从对象空间找,如果找不到,再从类空间找,再找不到,再从父类找...
- 类名.属性:先从本类空间找,如果找不到,再从父类找...
对象与对象之间是互相独立的.
面向对象的组合用法
组合指的是,给一个类的对象封装一个属性,这个属性是另一个类的对象
什么有什么的关系使用组合。比如:学生有书的关系,可以使用组合
圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。
这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用
from math import pi class Circle: """ 定义了一个圆形类; 提供计算面积(area)和计算周长(perimeter)的方法 """ def __init__(self, radius): self.radius = radius def area(self): return self.radius ** 2 * pi def perimeter(self): return self.radius * 2 * pi circle = Circle(3) # 实例化一个圆 area1 = circle.area() # 计算圆面积 per1 = circle.perimeter() # 计算圆周长 print(area1, per1) # 打印圆面积和周长 class Ring: def __init__(self, radius_outside, radius_inside): """ :param radius_outside: 外圆的半径 :param radius_inside: 内圆的半径 """ self.outside_redius = Circle(radius_outside) self.inside_redius = Circle(radius_inside) def rin_area(self): " 计算圆环的面积,用外圆的面积 - 内圆的面积" return self.outside_redius.area() - self.inside_redius.area() def rin_perimeter(self): " 计算圆环的周长,用外圆的周长 + 内圆的周长" return self.outside_redius.perimeter() + self.inside_redius.perimeter() ring = Ring(9, 3) # 实例化一个圆环 rinArea = ring.rin_area() # 计算圆环的面积 rinPer = ring.rin_perimeter() # 计算圆环的周长 print(rinArea, rinPer) # 打印圆环的面积和周长
""" 模拟英雄联盟写一个游戏人物的类. 要求: (1)创建一个 Game_role的类. (2) 构造方法中给对象封装name,ad(攻击力),hp(血量).三个属性. (3) 创建一个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): hp = p1.hp - self.ad print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, 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 def attack(self, p1): hp = p1.hp - self.ad print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, hp)) class Arms: def __init__(self, name, ad): self.name = name self.ad = ad def fight(self, p1, p2): hp = p2.hp - self.ad print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp)) gailun = Gamerole("盖伦", 10, 100) jianhao = Gamerole("剑豪", 20, 80) dabaodao = Arms("大宝刀", 20) dabaodao.fight(gailun, jianhao) # 版本二:通过组合来实现,让代码合理 class Gamerole: def __init__(self, name, ad, hp): self.name = name self.ad = ad self.hp = hp def armament_weapon(self, aex): # 调用时传入武器对象 self.aex = aex class Arms: def __init__(self, name, ad): self.name = name self.ad = ad def fight(self, p1, p2): hp = p2.hp - self.ad print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp)) gailun = Gamerole("盖伦", 10, 100) jianhao = Gamerole("剑豪", 20, 80) dabaodao = Arms("大宝刀", 20) gailun.armament_weapon(dabaodao) # 给盖伦装备了大宝刀这个武器 gailun.aex.fight(gailun, jianhao)
面向对象三大特征
面向对象有三大特性:继承、封装、和多态。
继承
什么是什么的关系使用继承,节省代码。比如:人是动物,狗也是动物,那么人和狗都可以继承动物这个类
继承: 单继承,多继承.
单继承
继承是一种创建新类的方式,在Python中,新建的类可以继承一个或者多个父类,父类又称为基类或超类,新建的类称为派生类或子类
子类可以自动拥有父类中除了私有属性外的其他所有内容。说⽩了, ⼉⼦可以随便用爹的东西。但是朋友们, 一定要认清楚⼀个事情。必须先有爹, 后有⼉⼦。 顺序不能乱,在Python中实现继承非常简单。在声明类的时候, 在类名后⾯面添加一个小括号,把要继承的类传进去就可以完成继承关系。
那么什么情况可以使用继承呢?
单纯的从代码层⾯上来看,当两个类具有相同的功能或者特征的时候,可以采用继承的形式。提取一个父类,这个父类中编写两个类中相同的部分。然后两个类分别去继承这个类就可以了。这样写的好处是我们可以避免写很多重复的功能和代码。
class Animal: def eat(self): print "%s 吃 " %self.name def drink(self): print "%s 喝 " %self.name def shit(self): print "%s 拉 " %self.name def pee(self): print "%s 撒 " %self.name class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print '喵喵叫' class Dog(Animal): def __init__(self, name): self.name = name self.breed = '狗' def cry(self): print '汪汪叫' # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
综合示例:
动物都有吃、喝、拉、撒共同属性,各动物有各动物的方法. 定义一个动物类(Animal),定义一个鸟类(Bird),定义一个马类(Horse)
class Animal(object): def __init__(self, name, sex, age): self.name = name self.sex = sex self.age = age def eat(self): print("%s 在吃东西" % self.name) def drink(self): print("%s 在喝东西" % self.name) def pull(self): print("%s 在拉粑粑" % self.name) def sow(self): print("%s 在嘘嘘" % self.name) class Bird(Animal): def flight(self): print("%s 在自由的飞翔" % self.name) class Horse(Animal): def running(self): print("%s 在飞驰的奔跑" % self.name) bird1 = Bird("麻雀", 1.2, "公") # 实例化一只鸟 bird1.flight() # 调用自身独有的方法 bird1.eat() # 调用父类公共的方法 horse1 = Horse("草原马", 3, "公") # 实例化一匹马 horse1.running() # 调用自身独有的方法 horse1.pull() # 调用父类公共的方法
现在要给鸟类添加一个翅膀的属性,给马类添加一个尾巴的属性
class Animal(object): def __init__(self, name, sex, age): self.name = name self.sex = sex self.age = age def eat(self): print("%s 在吃东西" % self.name) def drink(self): print("%s 在喝东西" % self.name) def pull(self): print("%s 在拉粑粑" % self.name) def sow(self): print("%s 在嘘嘘" % self.name) class Bird(Animal): def __init__(self, name, sex, age, wing): # 方法一:通过父类名.父类方法名(参数) # Animal.__init__(self, name, sex, age) # 方法二:通过super().父类方法名(参数(自动传入self)) super().__init__(name, sex, age) # 还可以这样写super(Bird,self).__init(name, sex, age) self.wing = wing def flight(self): print("%s 在自由的飞翔" % self.name) class Horse(Animal): def __init__(self, name, sex, age, tail): # Animal.__init__(self, name, sex, age) super().__init__(name, sex, age) self.tail = tail def running(self): print("%s 在飞驰的奔跑" % self.name) bird2 = Bird("鹦鹉", 6, "母", "红翅膀") # 实例化一只鸟,加入翅膀 print(bird2.__dict__) # 查看是否将翅膀属性成功添加 # {'name': '鹦鹉', 'sex': 6, 'age': '母', 'wing': '红翅膀'} horse2 = Horse("汗血马", 3, "公", "长尾巴") # 实例化一匹马,加入尾巴 print(horse2.__dict__) # 查看是否将尾巴属性成功添加 # {'name': '汗血马', 'sex': 3, 'age': '公', 'tail': '长尾巴'}
总结:
只执行父类的方法:子类中不要定义与父类同名的方法
只执行子类的方法:在子类创建这个方法
问题:既要执行子类的方法,又要执行父类的方法?
有两种解决方法:
1、在子类中执行父类的方法
- 父类名.父类方法名(参数)
2、通过super()
- super().父类方法名(参数(自传self))
多继承
类: 经典类, 新式类
- 新式类: 凡是继承object类都是新式类. python3x 所有的类都是新式类,因为python3x中的类都默认继承object.
class A(object): pass
- 经典类: 不继承object类都是经典类. python2x:(既有新式类,又有经典类) 所有的类默认都不继承object类,所有的类默认都是经典类.你可以让其继承object.
class A: pass
单继承: 新式类,经典类查询顺序一样
多继承:
- 新式类: 遵循广度优先. 广度优先 : 一条路走到倒数第二级,判断,如果其他路能走到终点,则返回走另一条路.如果不能,则走到终点.
- 经典类: 遵循深度优先. 深度优先 : 一条路走到底.
- 深度优先,广度优先:只能是继承两个类的情况
经典类多继承:
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): def func(self): print(" IN D") class E(C): def func(self): print(" IN E") class F(D, E): def func(self): print(" IN F") func1 = F() func1.func() # 执行func方法时: # 首先去F类找,如果F没有去D类找,如果D类没有去B类找,如果B类没有去A类找,如果A类没有去E类找,如果E类没有去C类找,如果C类还没有则报错 # 查找顺序为F——>D——>B——>A——>E——>C # 在上述查找func方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
新式类多继承:
class A(object): 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): def func(self): print(" IN D") class E(C): def func(self): print(" IN E") class F(D, E): def func(self): print(" IN F") func1 = F() func1.func() print(F.mro()) # 查询类的继承顺序 # 打印结果:[, # 执行func方法时: # 首先去F类找,如果F没有去D类找,如果D类没有去B类找,如果B类没有去E类找,如果E类没有去C类找,如果C类没有去A类找,如果A类还没有则报错 # 查找顺序为F——>D——>B——>E——>C——>A # 在上述查找func方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了, , , , , ]
继承总结
面向对象为什么要有继承?继承的好处是什么?
- 优化代码、节省代码
- 提高代码的复用性
- 提高代码的维护性
- 让类与类之间发生关系
抽象类与接口类
接口类
为什么要使用接口类?
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
- 归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备了某些功能就可以了,这极大的降低了使用者的使用难度。
- 归一化使得高层的外部使用者可以不加区分的处理多有接口兼容的对象集合。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
接口类示例
需求说明:开发一个支付功能、支持微信支付、支付宝支付、Apple支付等...
版本一:采用归一化设计
# 版本一:采用归一化设计 class Alypay(object): """ 支付宝支付 """ def pay(self, money): print("支付宝支付了 %s 元" % money) class Wechatpay(object): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(object): """ apple支付 """ def pay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() apy(a1, 200) apy(w1, 100) apy(p1, 50)
按照上面这种方法是可行的, 但是有个问题, 如果第二个程序员去更改你的代码,如果类中不是定义的pay函数去支付, 那么就会报错,列如下面将apple支付里面的函数(pay)变为(applepay).
版本二:
class Alypay(object): """ 支付宝支付 """ def pay(self, money): print("支付宝支付了 %s 元" % money) class Wechatpay(object): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(object): """ apple支付 """ def applepay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a2 = Alypay() w2 = Wechatpay() p2 = Applepay() apy(a2, 200) apy(w2, 100) apy(p2, 50) # 这里执行就会报错. 打破了归一化设计原则
版本三:通过abc模块来实现接口,如果类中定义的非模块指定的函数名,我们让其报错。
from abc import ABCMeta, abstractclassmethod class Payment(metaclass=ABCMeta): @abstractclassmethod def pay(self, money): pass class Alypay(Payment): """ 支付宝支付 """ def pay(self, money): print("支付宝支付了 %s 元" % money) class Wechatpay(Payment): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(Payment): """ apple支付 """ def Applepay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() # 执行报错: TypeError: Can't instantiate abstract class Applepay with abstract methods pay apy(a1, 200) apy(w1, 100) apy(p1, 50)
最终版本:
from abc import ABCMeta, abstractclassmethod class Payment(metaclass=ABCMeta): @abstractclassmethod def pay(self, money): pass class Alypay(Payment): """ 支付宝支付 """ def pay(self, money): print("支付宝支付了 %s 元" % money) class Wechatpay(Payment): """ 微信支付 """ def pay(self, money): print("微信支付了 %s 元" % money) class Applepay(Payment): """ apple支付 """ def pay(self, money): print("apple支付了 %s 元" % money) def apy(obj, money): obj.pay(money) a1 = Alypay() w1 = Wechatpay() p1 = Applepay() apy(a1, 200) apy(w1, 100) apy(p1, 50)
抽象类
- 什么是抽象类?
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化; Python抽象类是借助模块来实现
- 为啥要有抽象类?
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们又香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。你永远无法吃到一个叫做水果的东西。
从设计角度看,如果类是从现实对象抽取而来的,那么抽象类就是基于类抽象而来的。
从实现角度看,抽象类与普通类的不同之处在于:抽象类中有抽象的方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口类有点类似,但其实是不同的。
在Python中实现抽象类
# 一切皆文件 import abc # 利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type = 'file' @abc.abstractclassmethod # 定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractclassmethod # 定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1 = Txt() # 报错,子类没有定义抽象方法 # 报错内容 TypeError: Can't instantiate abstract class Txt with abstract methods read, write 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("进程数据的写方法") wenbenfile = Txt() satafile = Sata() processfile = Process() # 这样大家都被归一化了,也就是一切皆文件的思想 wenbenfile.read() satafile.write() processfile.read() print(wenbenfile.all_type) print(satafile.all_type) print(processfile.all_type)
抽象类与接口类的总结
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块来定义接口,我们也应该有一些基本的概念。
1、多继承问题
在继承抽象类的过程中,应该尽量避免多继承;
而在继承接口的时候,反而鼓励多继承接口
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
2、方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
多态
多态
多态指的是一类事物的多种形态。一种类型的多种形态 多个子类去继承父类,那么每一个子类都是这个父类的一种形态
动物有多种形态:人、狗、猪
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
文件有多种形态:文本文件、可执行文件
import abc class File(metaclass=abc.ABCMeta): #同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print('open file') class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print('execute file')
多态性
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性
peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
class TxtFile(object): def read(self): pass def write(self): pass class DiskFile(object): def read(self): pass def write(self): pass
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
class TxtFile(object): def read(self): pass def write(self): pass class DiskFile(object): def read(self): pass def write(self): pass # str, list, tuple 都是序列类型 s = str('hello') l = list([1, 2, 3]) t = tuple((4, 5, 6)) # 我们可以在不考虑三者类型的前提下使用s, l, t s.__len__() l.__len__() t.__len__() len(s) len(l) len(t)
封装
封装
- 隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处
- 将变化隔离;
- 便于使用;
- 提高复用性;
- 提高安全性;
封装原则
- 将不需要对外提供的内容都隐藏起来;
- 把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
在Python中使用双下划线开头的方式将属性隐藏起来(设置为私有的)
私有变量
# 其实这仅仅只是一种变形操作 # 类中所有双下划线开头的名称如__x 都会自动形成:_类名__x的新式: class A(object): name = "小白" __age = 18 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__age,会变形为_A__age def __init__(self): self.__x = 10 # 变形为self._A__X def __foo(self): # 变形为_A__foo print(self.__age) print("From A") def fun1(self): self.__foo() # 只有在类内部才可以通过__foo的形式访问到. obj1 = A() print(obj1.name) # print(obj1.__age) # 执行报错;因为实例化对象不能访问私有变量 # print(A.__age) # 执行报错;因为类名不能访问私有变量 # 对于私有变量,类的外部不能访问. obj1.fun1() # 对于私有静态变量,类的内部可以访问. print(A._A__age) # A._A__age是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
对于私有变量来说,只能在本类中内部访问,类的外部,派生类均不可访问.
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
class A(object): def func1(self): print(" In A ") def func2(self): self.func1() class B(A): def func1(self): print(" In B ") c1 = B() c1.func2() # 打印结果: In B class A(object): def __func1(self): print(" In A ") def func2(self): self.__func1() class B(A): def __func1(self): print(" In B ") c2 = B() c2.func2() # 打印结果: In A
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
# 类的设计者 class Room(object): def __init__(self, name, owner, length, width, high): self.name = name self.owner = owner self.__length = length self.__width = width self.__high = high def tell_area(self): return self.__length * self.__width # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 # 使用者 r1 = Room("客厅", "小白", 20, 20 ,20) area = r1.tell_area() # 使用者调用接口tell_area print(area) # 类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room(object): def __init__(self, name, owner, length, width, high): self.name = name self.owner = owner self.__length = length self.__width = width self.__high = high def tell_area(self): # 对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__length * self.__width * self.__high # 使用者 r1 = Room("客厅", "小白", 20, 20 ,20) area = r1.tell_area() # 对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 print(area)
属性
property 属性
属性:将一个方法伪装成一个属性;在代码的级别上没有本质的提升,但是让其看起来跟合理.
示例一:
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解) 成人的BMI数值: 过轻:低于18.5 正常:18.5-23.9 过重:24-27 肥胖:28-32 非常肥胖, 高于32 体质指数(BMI)=体重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86
class Bmi(object): def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def calculation_bmi(self): return "%s 的bmi 值%s" % (self.name, self.weight / self.height ** 2) p1 = Bmi("小白", 54, 1.65) # print(p1.calculation_bmi()) print(p1.calculation_bmi)
示例二:
import math class Circle(object): def __init__(self, radius): """ :param radius: 园的半径 """ self.radius = radius @property def area(self): return math.pi * self.radius ** 2 # 计算面积 @property def perimeter(self): return 2 * math.pi * self.radius # 计算周长 c = Circle(10) print(c.area) # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) # 同上 ''' 输出结果: 314.1592653589793 62.83185307179586 ''' #注意:此时的特性area和perimeter不能被赋值 c.area=3 #为特性area赋值 ''' 抛出异常: AttributeError: can't set attribute '''
为什么要使用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
列如
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
属性的改值:
class Person(object): def __init__(self, name, age): self.name = name self.__age = age if type(age) is int else print("您输入的年龄类型有误,请输入int类型") @property # 将一个方法伪装成一个属性 def age(self): return self.__age # obj.age访问的是self.__age(这也是真实值的存放位置) @age.setter def age(self, a1): """判断,修改的年龄必须是数字""" self.__age = a1 if type(a1) is int else print("您输入的年龄类型有误,请输入int类型") @age.deleter def age(self): del self.__age p1 = Person("小白", 20) print(p1.age) p1.age = 22 # 属性的改值, 一运行,就会自动执行类中的@age.setter下的函数 print(p1.age) del p1.age
一个静态属性property本质就是实现了get, set, delete三种方法
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('set的时候运行我啊') @AAA.deleter def AAA(self): print('delete的时候运行我啊') #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
怎么用?
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): # 修改商品原价 self.original_price = value @price.deleter def price(self): # 删除商品原价 del self.original_price clothes = Goods() clothes.price # 获取商品价格 print(clothes.price) clothes.price = 200 # 修改商品价格 print(clothes.price) del clothes.price # 删除商品价格
property 总结
property 装饰器函数,内置函数,帮助你将类中的方法伪装成属性,特性
调用方法的时候不需要主动加括号
让程序的逻辑性更合理
@方法名.setter 装饰器,修改被property装饰的属性的时候会调用被这个装饰器装饰的方法,除了self之外还有一个参数,被修改的值
@方法名.deleter 装饰器,当要删除被property装饰的属性的时候会调用被这个装饰器装饰的方法
类方法 classmethod
classmethod 类方法的装饰器 内置函数
使用类名调用,默认传类名作为第一个参数
不用对象命名空间中的内容,而用到了类命名空间中的变量(静态属性),或者类方法或静态方法
class A(object): def func(self): # 普通方法 print(self) @classmethod def func1(cls): # 类方法 print(cls) a1 = A() a1.func() # 类方法: 通过类名调用的方法,类方法中第一个参数约定俗称cls,python自动将类名(类空间)传给cls. A.func(a1) A.func1() a1.func1() # 对象调用类方法,cls 得到的是类本身.
类方法的应用场景:
- 类中 有些方法是不需要对象参与.
- 对类中的静态变量进行改变,要用类方法.
- 继承中,父类得到子类的类空间.
静态方法 staticmethod
class A: @staticmethod def login(username, password): if username == '小白' and password == 123: print('登录成功') else: print('登录失败...') A.login('小白',1234)