Python基础内容训练11(面向对象进阶)
面向对象进阶
一 综合案例
- 案例1 :简单的计算器
# 实现一个计算器可以进行简单的基本操作,以及打印结果
def jia(n1, n2):
return n1 + n2
def jian(n1, n2):
return n1 - n2
def cheng(n1, n2):
return n1 * n2
a = jia(1, 2)
print(a)
#例如计算(6+2-4)*5
n1 = jia(6,2)
n2 = jian(n1,4)
n3 = cheng(n2,5)
print(n3)
- 改进1
我们可以看到上面的综合计算的步骤太麻烦了,还得计算中间结果,下面我们来对它进行改进
下面的改进实际上是用了result来保存中间结果,这样调用的函数的时候就简单一点
result = 0
def first_value(v):
global result
result = v
def jia(n):
# 为了能让result可以在函数中修改,把他变成全局变量
global result
result += n
def jian(n):
global result
result -= n
def cheng(n):
global result
result *= n
# 计算(2+6-4)*5
first_value(2)
jia(6)
jian(4)
cheng(5)
print(result)
- 改进2
我们上面的方式可以说是面向过程的,我们可以看到首先就是很麻烦,其次就是result是全局变量,如果我们在下面把这个全局变量修改会导致结果错误,那我们把这个计算器的功能给修改了,使用面向对象的方法进行封装。
下面我们对这个代码来进行分析,我们封装之后面临着两个问题就是首先这个result是变量,那面向对象处理之后就是把它作为属性,是类属性还是实例(对象属性)呢,我们把它设置为类属性,这样就可以通过类.属性名来进行调用;第二个问题就是函数我们怎么样修改呢,修改成方法有三个选择,类方法,实例方法和静态方法,如果要使用对象方法或者说是实例方法的时候,会传个self,但是呢这个self没有什么作用,而且我们想要调用的时候可以直接使用类.方法就行,所以我们选用的是类方法;并且为了这个全局变量result不能别修改,我们将它私有化,但是私有化了之后不能去访问,所以我们加了一个show方法,可以展示result结果。
class Caculator():
__result = 0
@classmethod
def first_value(cls,v):
cls.__result = v
@classmethod
def jia(cls,n):
cls.__result += n
@classmethod
def jian(cls,n):
cls.__result -= n
@classmethod
def cheng(cls,n):
cls.__result *= n
@classmethod
def show(cls):
print(f'计算的结果是:{cls.__result}')
Caculator.first_value(2)
Caculator.jia(6)
Caculator.jian(4)
Caculator.cheng(5)
Caculator.show()
- 改进3
这个result是类属性,它是依附于类的,这个类是在内存中只有一个的,所以result无法进行同时进行多个表达式的运算;同时操作的时候是同一个对象;如果变成对象属性就可以避免这种情况,同时一旦result属性变成对象属性,下面的方法也要变成实例方法
class Caculator():
def __init__(self,result):
self.__result = result
def first_value(self,v):
self.__result = v
def jia(self,n):
self.__result += n
def jian(self,n):
self.__result -= n
def cheng(self,n):
self.__result *= n
def show(self):
print(f'计算的结果是:{self.__result}')
c1 = Caculator(2)
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
- 改进4-加上验证、容错处理
class Caculator():
def check_num(self,num):
if not isinstance(num,int):
raise TypeError('您输入的类型错误')
def __init__(self,result):
self.check_num(result)
self.__result = result
def first_value(self,v):
self.check_num(v)
self.__result = v
def jia(self,n):
self.check_num(n)
self.__result += n
def jian(self,n):
self.check_num(n)
self.__result -= n
def cheng(self,n):
self.check_num(n)
self.__result *= n
def show(self):
print(f'计算的结果是:{self.__result}')
c1 = Caculator('abc')
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
- 改进5-上面的验证方法改变了代码原本的功能,我们可以使用装饰器来解决,可以加一些额外的功能,并且不会破坏原本的代码。
装饰器既可以通过类来实现,也可以通过函数来实现,这里先暂时先选择函数实现
class Caculator():
#接受一个func,一个外界所传递过来的函数,也就是要装饰的函数
def check_num_zsq(func):
#内部呢,要定义一个闭包,要保证这个inner里面和下面加减乘所接受到的参数完全一样
def inner(self,n):
if not isinstance(n,int):
raise TypeError('您输入的类型错误')
#如果是整形,直接调用这个装饰的函数就可以了,并且把返回值返回给外界
return func(self,n)
#返回inner但是不要写小括号,小括号代表执行,不写小括号代表把函数给外界
return inner
@check_num_zsq
def __init__(self,result):
self.__result = result
@check_num_zsq
def first_value(self,v):
self.__result = v
@check_num_zsq
def jia(self,n):
self.__result += n
@check_num_zsq
def jian(self,n):
self.__result -= n
@check_num_zsq
def cheng(self,n):
self.__result *= n
def show(self):
print(f'计算的结果是:{self.__result}')
c1 = Caculator(2)
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
- 改进6,已经写好了装饰器,装饰器本质讲就是一个函数,而这个函数写在类的内部是属于一个实例方法,外界可以通过实例来调用这个装饰器,但是没有什么意义,并且没什么用;那么怎么样不让外界调用这个装饰器呢?直接把它变成一个私有的方法
外界访问
class Caculator():
#接受一个func,一个外界所传递过来的函数,也就是要装饰的函数
def __check_num_zsq(func):
#内部呢,要定义一个闭包,要保证这个inner里面和下面加减乘所接受到的参数完全一样
def inner(self,n):
if not isinstance(n,int):
raise TypeError('您输入的类型错误')
#如果是整形,直接调用这个装饰的函数就可以了,并且把返回值返回给外界
return func(self,n)
#返回inner但是不要写小括号,小括号代表执行,不写小括号代表把函数给外界
return inner
@__check_num_zsq
def __init__(self,result):
self.__result = result
@__check_num_zsq
def first_value(self,v):
self.__result = v
@__check_num_zsq
def jia(self,n):
self.__result += n
@__check_num_zsq
def jian(self,n):
self.__result -= n
@__check_num_zsq
def cheng(self,n):
self.__result *= n
def show(self):
print(f'计算的结果是:{self.__result}')
c1 = Caculator(2)
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
- 改进7-新增功能,输入的时候,加上语音播报,主要问题是这个语音播报写在那个位置,并且如何调用
通过一个实例去调用某个实例方法,他会自动的往里面传第一个参数self,所以方法要加一个参数self
如果下面win32com出现包找不到代码,可以先通过下面操作进行导入
python -m pip install pypiwin32
import win32com.client
#1. 创建一个播报器对象
speaker = win32com.client.Dispatch('SAPI.SpVoice')
#2. 通过这个播报器对象,直接播放相对应的语音字符串即可
speaker.Speak('我的名字是sz')
class Caculator():
def __say(self,word):
# 1. 创建一个播报器对象
speaker = win32com.client.Dispatch('SAPI.SpVoice')
# 2. 通过这个播报器对象,直接播放相对应的语音字符串即可
speaker.Speak(word)
#接受一个func,一个外界所传递过来的函数,也就是要装饰的函数
def __check_num_zsq(func):
#内部呢,要定义一个闭包,要保证这个inner里面和下面加减乘所接受到的参数完全一样
def inner(self,n):
if not isinstance(n,int):
raise TypeError('您输入的类型错误')
#如果是整形,直接调用这个装饰的函数就可以了,并且把返回值返回给外界
return func(self,n)
#返回inner但是不要写小括号,小括号代表执行,不写小括号代表把函数给外界
return inner
@__check_num_zsq
def __init__(self,result):
self.__say(result)
self.__result = result
@__check_num_zsq
def first_value(self,v):
self.__say(v)
self.__result = v
@__check_num_zsq
def jia(self,n):
self.__say(n)
self.__result += n
@__check_num_zsq
def jian(self,n):
self.__say(n)
self.__result -= n
@__check_num_zsq
def cheng(self,n):
self.__say(n)
self.__result *= n
def show(self):
self.__say(f'计算的结果是:{self.__result}')
print(f'计算的结果是:{self.__result}')
c1 = Caculator(2)
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
- 改进8-上面的代码还是一样的问题就是代码内容改了,所以为了保证代码的内容不被修改,我们继续可以使用装饰器来进行操作
class Caculator():
#接受一个func,一个外界所传递过来的函数,也就是要装饰的函数
def __check_num_zsq(func):
#内部呢,要定义一个闭包,要保证这个inner里面和下面加减乘所接受到的参数完全一样
def inner(self,n):
if not isinstance(n,int):
raise TypeError('您输入的类型错误')
#如果是整形,直接调用这个装饰的函数就可以了,并且把返回值返回给外界
return func(self,n)
#返回inner但是不要写小括号,小括号代表执行,不写小括号代表把函数给外界
return inner
def __say_zsq(func):
def inner(self,n):
# 1. 创建一个播报器对象
speaker = win32com.client.Dispatch('SAPI.SpVoice')
# 2. 通过这个播报器对象,直接播放相对应的语音字符串即可
speaker.Speak(n)
return func(self,n)
return inner
@__check_num_zsq
@__say_zsq
def __init__(self,result):
self.__result = result
@__check_num_zsq
@__say_zsq
def first_value(self,v):
self.__result = v
@__check_num_zsq
@__say_zsq
def jia(self,n):
self.__result += n
@__check_num_zsq
@__say_zsq
def jian(self,n):
self.__result -= n
@__check_num_zsq
@__say_zsq
def cheng(self,n):
self.__result *= n
def show(self):
self.__say(f'计算的结果是:{self.__result}')
print(f'计算的结果是:{self.__result}')
c1 = Caculator(2)
c1.jia(6)
c1.jian(4)
c1.cheng(5)
c1.show()
二 面向对象的三大特性之封装
概念:将一些属性和方法封装到一个对象中,对外呢隐藏内部具体的实现细节
三 面向对象的三大特性之继承
继承父类非私有的属性和方法
class Animal():
pass
class Person():
pass
#单继承
class Dog(Animal):
pass
#多继承
class Monkey(Animal,Person):
pass
- 继承的资源的使用:私有的属性和方法都无法访问,也就是带两个下划线的都不能访问;并且在python里面所谓的继承是指子类能不能使用父类的资源,并不是复制。
class A:
age = 9
class B(A):
pass
print(A.age)
print(B.age)
B.age = 10
print(A.age)
print(B.age)
print(A.__dict__)
print(B.__dict__)
根据代码我们可以看出来,这个B.age相当于给B添加了一个属性,B继承了A,B可以读取A的资源而不是设置
- 常见的继承形态
单继承:从下往上
对于多继承链:它会遵循单调原则,如上图所示,会优先先到左侧B找,B没有再到D;然后到右侧寻找
对于有重叠的多继承链:从下到上,A没有去优先左侧B,B没有再到C里面找,C里面没有到D里面去寻找
在Python2.2之前的版本中遵循的原则是深度优先遍历,违背了重写可用原则,尤其是对于有重叠的多继承链
- 资源的覆盖
如下图所示,根据继承的规则我们知道访问顺序应该是A,B,C,D,B和C同时都有XXX,根据访问机制,C的XXX是无法覆盖掉B的XXX的。
注意事项:当调用优先级比较高的资源的时候,注意self的变化,比如说B里面有个实例方法,还有一个类方法,这时候A去调用这个两个方法,传进来的实际上是A,用实例a去调用呢,实际上也是A方法,也就是说谁调用这个方法传进来的就是谁。
- 资源的累加
注意一点:比如A继承它的父类B的时候,B里面有一个init方法,里面会有个实例属性a,A创建的对象可以访问这个a,这时候给A也加上一个init方法,里面有个属性b,这时候用对象访问a就不可以了,就会报错;因为对于对象a来说先进行访问A类的init方法,不会访问B的init方法,所以没有属性a
class B:
age = 18
def __init__(self):
self.a = 1
class A(B):
def __init__(self):
self.b = 2
aa = A()
print(aa.b)
print(aa.a)
其实可以避免这个情况就是用super(还有在A里面加一个用类B调用init,然后把self传进去的方式,但是不利于代码的维护)
下面介绍super的用法
这里的第二个参数一般是前一个参数的子类,或者实例对象,但是这个实例对象对应的类应该是第一个参数的子类或者第一个参数的本身;第一个参数是找谁的下一个节点。
class B:
a = 1
def __init__(self):
self.b = 2
def t1(self):
print('t1')
@classmethod
def t2(cls):
print('t2')
@staticmethod
def t3():
print('t3')
class A(B):
c = 3
def __init__(self):
# 这里我们看到要找A的下一个节点,第一个参数就传A
#对于第二个参数看沿着谁的链条,不能是B,因为B之后没有A了,只能是A(A的对象)或者A的子类(A子类对象)
#对于B的init这个方法,我们到时候要给这个实例方法里面传递参数self,是个实例对象;要是调用类方法传递的就是类了
#A的实例对象就是self
# super(A, self).__init__()
#上面的写法等同于下面,简化了会自动判断填充的
super().__init__()
self.e = 6
def tt1(self):
print('tt1')
@classmethod
def tt2(cls):
#这里因为是类方法(往B里面传的是类),所以第二个参数传入的是类,只能是A或者A的子类,这里只能写A
super(A,A).t2()
print('tt2')
@staticmethod
def tt3():
print('tt3')
a = A()
print(a.__dict__)
A.tt2()
根据结果我们可以看到是成功的
下面再看菱形结构,就是A的父类是两个的情况,我们说用类调用的方式会产生重复调用,所以才引入super方法,下面我们看一下super方法在菱形结构的调用
class D():
def __init__(self):
print('d')
class B(D):
def __init__(self):
D.__init__(self)
print('b')
class C(D):
def __init__(self):
D.__init__(self)
print('c')
class A(B,C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print('a')
A()
我们可以看到之前的方法D类会被调用两次,下面我们用super的方式来解决下
class D():
def __init__(self):
print('d')
class B(D):
def __init__(self):
super().__init__()
print('b')
class C(D):
def __init__(self):
super().__init__()
print('c')
class A(B,C):
def __init__(self):
# B.__init__(self)
# C.__init__(self)
super().__init__()
print('a')
A()
注意事项:不要尝试通过实例对象self动态的去获取他的类,因为到时候这个实例对象self’到底是谁是不知道的,如果他是父类,有可能是它子类的实例对象,所以有可能会产生死循环;第二个注意事项到时候在子类里面尝试去调用父类资源的时候,不要把用类名来调用和通过super来调用进行混合使用,因为到时候有可能会产生一些混乱
四 面向对象的三大特性之多态
多态就是一个类所延伸的多种形态
下面这个例子,方法都是一样的,只是所接受的对象不一样,对象拥有不同的形态就产生了不同的行为。
class Animal():
def jiao(self):
pass
class Dog(Animal):
def jiao(self):
print('汪汪汪')
class Cat(Animal):
def jiao(self):
print('喵喵喵')
def test(obj):
obj.jiao()
d = Dog()
c = Cat()
test(c)
- 抽象类和抽象方法
对于上面的例子来说,Animal就是一个抽象的类,你可以说创建一个小狗对象,一个小猫对象这样的具体的对象,而不能说创建一个Animal对象,而叫就是一个抽象方法
import abc
class Animal(object,metaclass=abc.ABCMeta):
@abc.abstractmethod
def jiao(self):
pass
@abc.abstractclassmethod
def test(cls):
pass
class Dog(Animal):
def jiao(self):
print('汪汪汪')
@classmethod
def test(cls):
print('xxx')
class Cat(Animal):
def jiao(self):
print('喵喵喵')
def test(obj):
obj.jiao()
d = Dog()
d.jiao()
d.test()
五 综合案例
先定义小狗类:
class Dog:
#age设置为如果不写年龄默认为1
def __init__(self,name,age = 1):
self.name = name
self.age = age
def eat(self):
print(f'叫{self.name},{self.age}岁的小狗正在吃东西')
def play(self):
print(f'叫{self.name},{self.age}岁的小狗正在玩')
def sleep(self):
print(f'叫{self.name},{self.age}岁的小狗正在睡觉')
def watch(self):
print(f'叫{self.name},{self.age}岁的小狗正在看家')
d = Dog('小黑',10)
d.play()
- 改进1,我们可以看到之前写的print函数里面好多是重复的,所以我们可以选用之前学的str来提取公共的话语来简化,str表示self字符串本身它的字符串描述,到时候只需要把这个字符串返回出去即可
class Dog:
#age设置为如果不写年龄默认为1
def __init__(self,name,age = 1):
self.name = name
self.age = age
def eat(self):
print(f'{self}吃东西')
def play(self):
print(f'{self}玩')
def sleep(self):
print(f'{self}睡觉')
def watch(self):
print(f'{self}看家')
def __str__(self):
return f'叫{self.name},{self.age}的小狗正在'
d = Dog('小黑',10)
d.play()
- 改进二:加上猫和人这两个类别
class Person:
#这种存在默认值的放在最后面
def __init__(self,name,pets,age = 18):
self.name = name
self.age = age
self.pets = pets
def eat(self):
print(f'{self}吃东西')
def play(self):
print(f'{self}玩')
def sleep(self):
print(f'{self}睡觉')
#其实就是让所有的宠物吃饭,睡觉,玩
def carePets(self):
for pet in self.pets:
pet.eat()
pet.play()
pet.sleep()
#遍历拥有的宠物,根据宠物的类别不同让宠物进行各自的工作
def make_pets_work(self):
for pet in self.pets:
if isinstance(pet,Dog):
pet.watch()
elif isinstance(pet,Cat):
pet.catch()
def __str__(self):
return f'叫{self.name},{self.age}的小狗正在'
class Cat:
#age设置为如果不写年龄默认为1
def __init__(self,name,age = 1):
self.name = name
self.age = age
def eat(self):
print(f'{self}吃东西')
def play(self):
print(f'{self}玩')
def sleep(self):
print(f'{self}睡觉')
def catch(self):
print(f'{self}捉老鼠')
def __str__(self):
return f'叫{self.name},{self.age}的小猫正在'
class Dog:
#age设置为如果不写年龄默认为1
def __init__(self,name,age = 1):
self.name = name
self.age = age
def eat(self):
print(f'{self}吃东西')
def play(self):
print(f'{self}玩')
def sleep(self):
print(f'{self}睡觉')
def watch(self):
print(f'{self}看家')
def __str__(self):
return f'叫{self.name},{self.age}的小狗正在'
d = Dog('小黑',10)
c = Cat('小红',6)
p = Person('sz',[d,c],18)
p.carePets()
p.make_pets_work()
- 改进三:我们知道如果一个人拥有很多类别的宠物的时候,这个代码就要继续加判断,很麻烦,所以我们在宠物的方法中统一成work方法,这样就不用判断了
- 改进四:每个类有很多是相同的,所以我们可以使用三大特性之继承特性,写一个父类,把公共方法什么的写到父类里面去,然后用别的去继承,再加上各自的单独的属性即可
属性:公共的:姓名、年龄;人有个特殊的属性:宠物
方法:公共的:吃饭、谁家、玩;各自的:狗:看家;猫:捉老鼠;人:养宠物和让宠物工作
class Animial():
def __init__(self, name, age=18):
self.name = name
self.age = age
def eat(self):
print(f'{self}吃东西')
def play(self):
print(f'{self}玩')
def sleep(self):
print(f'{self}睡觉')
class Person(Animial):
#加super是继承父类的属性
def __init__(self,name,pets,age = 18):
super(Person,self).__init__(name,age)
self.pets = pets
def carePets(self):
for pet in self.pets:
pet.eat()
pet.play()
pet.sleep()
#遍历拥有的宠物,根据宠物的类别不同让宠物进行各自的工作
def make_pets_work(self):
for pet in self.pets:
pet.work()
def __str__(self):
return f'叫{self.name},{self.age}的小狗正在'
class Cat(Animial):
def work(self):
print(f'{self}捉老鼠')
def __str__(self):
return f'叫{self.name},{self.age}的小猫正在'
class Dog(Animial):
def work(self):
print(f'{self}看家')
def __str__(self):
return f'叫{self.name},{self.age}的小狗正在'
d = Dog('小黑',10)
c = Cat('小红',6)
p = Person('sz',[d,c],18)
p.carePets()
p.make_pets_work()
五 设计原则
单一职责:一个类只负责一项职责
就是任何地方你都可以使用子类对象去替代这个父类对象
例如下面这个例子,父类是小鸟会飞,但是子类鸵鸟是不会飞的,又想让鸵鸟继承小鸟,怎么办呢?把小鸟分成会飞的小鸟和不会飞的小鸟,让鸵鸟去继承不会飞的小鸟即可。