第七章 面向对象深入
在本章我们首先学习了面向对象的三大特征, 继承, 封装和多态. 主要讲述了继承和多态: 继承某个父类, 可以实现并重写父类方法, 并且所有类都继承父类Object, 因此可以使用很多Object提供的特殊方法和特殊属性; 而多态指的的是不同子对象在调用父类方法时会有不同体现, 核心是: 继承和方法重写. 多态的出现也符合面向对象的"开闭原则" 封装之所以没讲是因为我们经常会用到, 因为在我们日常编程中, 将一些具有特定功能的代码块封装成方法/函数这种行为正体现了封装思想 然后学习了深拷贝和浅拷贝: 浅拷贝后对象的地址改变, 但子对象地址不改变. 深拷贝: 拷贝对象的地址改变, 子对象地址也改变 再然后我们了解编程中常用的工厂模式, 单例模式以及他们的组合, 最后通过实操来回顾下本章学习的内容
Python是面向对象的语言 而面向对象编程具有三大特性:继承、封装(隐藏)、多态
具体含义如下
继承是面向对象编程的三大特征之一. 继承让我们更加容易实现类的扩展. 实现代码的重用,不用再重新发明轮子
语法格式
class 子类类名(父类1[,父类2,...]):
类体
注意
如果在类定义中没有指定父类,则默认父类是 object类.
也就是说 object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如: __new__()
等
关于构造函数:
__init__
时,实例化子类,会自动调用父类定义的 __init__
。__init__
时,实例化子类,就不会调用父类已经定义的 __init__
__init__
时,要使用父类的构造方法,可以使用 super
关键字,也可以使用如下格式调用: 父类名.__init__(self, 参数列表)
实操代码
import copy
print("======================测试继承===========================")
class Person:
def __init__(self, name, age):
print("Person构造方法")
self.name = name
self.age = age
def print_age(self):
print("姓名是: {0}, 年龄是: {1}".format(self.name, self.age))
# 这种方式相当于继承, 这里指的是Student继承Person
class Student(Person):
"""
关于构造函数:
子类不重写 __init__ ,实例化子类时,会自动调用父类定义的 __init__ 。
子类重写了 __init__ 时,实例化子类,就不会调用父类已经定义的 __init__
如果重写了 __init__ 时,要使用父类的构造方法,可以使用 super 关键字,也可以使用如下格式调用: 父类名.__init__(self, 参数列表)
"""
def __init__(self, name, age, score):
print("Student的构造方法")
# 方式一: 通过类名.__init__重写父类构造
Person.__init__(self, name, age)
# 方式二: 通过super重写父类构造
# super(Student, self).__init__(name, age)
# self.name = name
# self.age = age
self.score = score
# s1 = Student("TimePause", 18) # 子类没有重写构造方法可以这样调用
s1 = Student("TimePause", 18, 149)
s1.print_age()
print(dir(s1))
在子类中,如果想要获得父类的方法时,我们可以通过 super() 来做. super() 代表父类的定义,不是父类对象 调用父类的构造方法:
super(子类名称,self).__init__(参数列表)
print("=====================super()获得父类定义==============================")
class A:
def __init__(self):
print("A的构造方法")
def say(self):
print(self)
print("I said, no one speaks better than me")
class B(A):
def __init__(self):
super(B, self).__init__() # 调用父类的构造方法
print("B的构造方法")
def say(self):
A.say(self) # 1.调用父类的say方法
# super().say() # 2.通过super 调用父类的方法
print("bb")
b1 = B()
b1.say()
object 类是所有类的父类,因此所有的类都有 object 类的属性和方法
而内置函数 dir() : 可以让我们方便的看到指定对象所有的属性
【实操】查看对象所有属性以及和 object 进行比对
print("==========================object根类============================")
print("========================dir() 查看对象属性============================")
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def print_age(self):
print(self.name, "的年龄是:", self.age)
obj = object()
print(dir(obj))
s2 = Person("时间静止", 18)
print(dir(s2))
从上面代码在控制台的输出我们可以发现这样几个要点:
Python支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将“从左向右”按顺序搜索 MRO(Method Resolution Order):方法解析顺序. 我们可以通过 mro() 方法获得“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找的
实操代码
print("============================MRO方法解析顺序=========================")
class A:
def aa(self):
print("aa")
def say(self):
print("I said, no one speaks better than me")
class B:
def bb(self):
print("bb")
class C(A, B):
def cc(self):
print("cc")
c1 = C()
"""打印类的层次结构"""
print(C.mro())
""" 解释器寻找方法是“从左到右”的方式寻找,此时会执行B类中的say()"""
c1.say()
运行结果
除了上面方法,Object 父类还定义了一些特殊方法, 用于对方法, 属性等进行相关操作
常用的特殊方法统计如下:
在python中, 每个运算符实际上都有Object相对应的方法
最常用的一些统计如下:
实操代码
print("================特殊方法和运算符重载========================")
"""Python的运算符实际上是通过调用对象的特殊方法实现的"""
a = 20
b = 30
c = a + b
d = a.__add__(b)
print("c=", c)
print("d=", d)
print("================测试运算符的重载========================")
class Person:
def __init__(self, name):
self.name = name
def __add__(self, other):
if isinstance(other, Person):
return "{0}--{1}".format(other.name, self.name)
else:
return "不是对象不能相加"
def __mul__(self, other):
if isinstance(other, int):
return self.name * other
else:
return "不是相同对象不能相乘"
p1 = Person("时间静止")
p2 = Person("TimePause")
print(p1 + p2)
print(p1 * 3)
Python支持多重继承,一个子类可以有多个“直接父类”. 这样就具备了“多个父类”的特点. 但是这样会把“类的整体层次”搞的异常复杂,因此尽量避免使用
语法格式
class 子类类名(父类1,父类2[,...]):
类体
实操代码
print("============================多继承========================================")
class A:
"""
Python支持多重继承,一个子类可以有多个“直接父类”。
这样,就具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用
"""
def aa(self):
print("aa")
class B:
def bb(self):
print("bb")
class C(A, B): # 类C同时继承了A,B
def cc(self):
print("cc")
c1 = C()
c1.cc()
c1.bb()
c1.aa()
结婚就是组合。两人组合后,可以复用对方的属性和方法
is-a
关系,我们可以使用“继承”. 从而实现子类拥有的父类的方法和属性.
is-a 关系指的是类似这样的关系:狗是动物,dog is animal. 狗类就应该继承动物类has-a
关系,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性
has-a 关系指的是这样的关系:手机拥有CPU. MobilePhone has a CPU实操代码
print("===============================组合===============================")
class MobilePhone:
def __init__(self, cpu, screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("计算,算个12345")
class Screen:
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金大眼")
c = CPU()
s = Screen()
m = MobilePhone(c, s) # 在创建对象时进行组合操作
m.cpu.calculate() # 通过组合,我们也能调用cpu对象的方法。相当于手机对象间接拥有了“cpu的方法”
m.screen.show() # 通过组合,我们也能调用screen对象的方法。相当于手机对象间接拥有了“screen的方法”
多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为 现实生活中,同一个方法具体实现会完全不同. 比如:同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭
注意:
实操代码
print("=================================多态======================================")
class Animal:
def shout(self):
print("动物叫")
class Dog(Animal):
def shout(self):
print("小狗叫, 汪汪汪")
class Cat(Animal):
def shout(self):
print("小猫叫, 咪咪咪")
def animalShout(a):
"""传入的对象不同,shout方法对应的实际行为也不同"""
a.shout()
animalShout(Dog())
animalShout(Cat())
如图所示:
实操代码
print("=======================对象的浅拷贝和深拷贝================================")
class MobilePhone:
def __init__(self, cpu):
self.cpu = cpu
class CPU:
pass
c = CPU()
m = MobilePhone(c)
print("----浅拷贝: 拷贝对象的地址改变, 但子对象地址不改变-------")
m2 = copy.copy(m) # m2是新拷贝的另一个手机对象
print("m:", id(m))
print("m2:", id(m2))
print("m的cpu:", id(m.cpu))
print("m2的cpu:", id(m2.cpu)) # m2和m拥有了一样的cpu对象
print("----深拷贝: 拷贝对象的地址改变, 子对象地址也改变--------")
m3 = copy.deepcopy(m)
print("m:", id(m))
print("m3:", id(m3))
print("m的cpu:", id(m.cpu))
print("m3的cpu:", id(m3.cpu)) # m3和m拥有不一样的cpu对象
设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法, 设计模式有很多种,比较流行的是:GOF(GoupOf Four)23种设计模式. 我们没有必要全部学习,只需学习几个常用的即可 对于初学者,我们学习两个最常用的模式:工厂模式和单例模式
工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制
注意事项
实操代码
print("=======================工厂模式=========================")
class Benz:
pass
class BMW:
pass
class BYD:
pass
class CarFactory:
def createCar(self, brand):
if brand == "奔驰":
return Benz()
elif brand == "宝马":
return BMW()
elif brand == '比亚迪':
return BYD()
else:
return "未知品牌,无法创建"
factory = CarFactory()
c2 = factory.createCar("宝马")
c1 = factory.createCar("奔驰")
print(c2)
print(c1)
单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点 单例模式只生成一个实例对象,减少了对系统资源的开销. 当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时, 可以产生一个“单例对象”,然后永久驻留内存中,从而极大的降低开销 单例模式有多种实现的方式,我们这里推荐重写
__new__()
方法
注意事项
__new__()
方法 ( __new__()
作用是定义并返回实例化的类, 执行顺序: new() -> init())__new__()
方法, 如果没有创建对象, 则通过 object.__new__(cls)
创建该类对象. 否则执行下一步
重写 __init__()
方法, 用于初始化该类, 并初始化类属性实操代码
print("==========================单例模式===================================")
class MySingleton:
__obj = None
__init_flag = True
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self, name):
if MySingleton.__init_flag:
print("init....")
self.name = name
MySingleton.__init_flag = False
a = MySingleton("aa")
print(a)
b = MySingleton("bb")
print(b)
运行结果
设计模式称之为“模式”,就是一些固定的套路 我们很容易用到其他场景上,比如前面讲的工厂模式,我们需要将工厂类定义成“单例”,只需要简单的套用即可实现
实操代码
class BenZ:
pass
class BMW:
pass
class BYD:
pass
class CarFactory:
"""定义两个类属性"""
__obj = None
__init_flag = True
"""定义工厂方法: 输入对应的商标, 返回对应的对象"""
def create_car(self, brand):
if brand == "奔驰":
return BenZ()
elif brand == "宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "未知品牌, 无法创建"
"""定义原型模式方法"""
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
# cls.__obj == object.__new__(cls) # 这里使用的是赋值语句而不是等值语句
cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self):
if CarFactory.__init_flag:
print("初始化工厂模式")
CarFactory.__init_flag = False
factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
print(c1)
print(c2)
print("这里可以看到根据输入的内容创建了不同的对象")
factory2 = CarFactory()
print(factory)
print(factory2)
print("这里可以看到虽然我们又新建了一个工厂对象, 但是我们使用的仍是同一个工厂对象")
运行结果
定义发动机类Motor、底盘类Chassis、座椅类Seat,车辆外壳类Shell,并使用组合关系定义汽车类. 其他要求如下:
定义汽车的run()方法,里面需要调用Motor类的work()方法,需要调用座椅类Seat的work()方法,
也需要调用底盘类Chassis的work()方法
设计模式选择依据:
多态实现: 因为这些汽车组件都有一个run() 方法. has-a + 继承 可以使用多态实现
工厂模式: 因为有多个组件, 可以通过工厂模式进行创建
实操代码
print("=====================方式一:多态实现======================")
class Component:
def work(self):
print("汽车组件工作")
class Motor(Component):
def work(self):
print("发动机工作")
class Chassis(Component):
def work(self):
print("底盘工作")
class Seat(Component):
def work(self):
print("座椅工作")
class Shell(Component):
def work(self):
print("外壳工作")
def run(part):
part.work()
run(Motor())
run(Seat())
run(Chassis())
print("====================方式二: 工厂模式实现=======================")
class CarFactory:
"""定义类属性"""
__init_flag = True
__obj = None
"""定义工厂方法"""
def run(self, component):
if component == "发动机":
return Motor()
elif component == "底盘":
return Chassis()
elif component == "座椅":
return Seat()
elif component == "外壳":
return Shell()
"""定义原型模式方法"""
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self):
if CarFactory.__init_flag:
print("初始化汽车工厂")
CarFactory.__init_flag = False
car1 = CarFactory()
car1.run("发动机").work()
car1.run("座椅").work()
car1.run("底盘").work()
运行结果
使用工厂模式、单例模式实现如下需求:
(1) 电脑工厂类ComputerFactory用于生产电脑Computer。工厂类使用单例模式,也就是说只能有一个工厂对象
(2) 工厂类中可以生产各种品牌的电脑:联想、华硕、神舟
(3) 各种品牌的电脑使用继承实现:
(4) 父类是Computer类,定义了calculate方法
(5) 各品牌电脑类需要重写父类的calculate方法
实操代码
# 定义父类
class Computer:
def calculate(self):
print("计算方法, 用于被重写")
class Lenovo(Computer):
def calculate(self):
print("联想进行电脑计算")
class ASUS(Computer):
def calculate(self):
print("华硕电脑进行计算")
class ShenZhou(Computer):
def calculate(self):
print("神州电脑进行计算")
class ComputerFactory:
"""类属性"""
__obj = None
__init_flag = True
"""工厂方法"""
def productionComputer(self, brand):
if brand == "联想":
return Lenovo()
elif brand == "华硕":
return ASUS()
elif brand == "神州":
return ShenZhou()
else:
return "输入错误, 无法创建"
"""单例模式方法"""
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
return object.__new__(cls)
def __init__(self):
if ComputerFactory.__init_flag:
print("开始初始化")
ComputerFactory.__init_flag = False
computer1 = ComputerFactory()
print("测试工厂方法")
computer1.productionComputer("联想").calculate()
computer1.productionComputer("华硕").calculate()
computer1.productionComputer("神州").calculate()
print("测试单例方法")
computer2 = ComputerFactory()
print(computer1)
print(computer2)
执行结果
定义一个Employee雇员类,要求如下:
(1) 属性有:id、name、salary
(2) 运算符重载+:实现两个对象相加时,默认返回他们的薪水和
(3) 构造方法要求:输入name、salary,不输入id. id采用自增的方式,从1000开始自增,第一个新增对象是1001,第二个新增对象是1002
(4) 根据salary属性,使用@property设置属性的get和set方法。set方法要求输入:1000-50000范围的数字
实操代码
class Employee:
__id = 1000
def __init__(self, name, salary):
"""利用类属性实现id自增"""
self.name = name
self.__salary = salary # 因为salary要在多个方法中使用, 因此要设置成类变量而不是设置成局部变量/方法内变量
self.id = Employee.__id + 1
Employee.__id = self.id
def __add__(self, other):
if isinstance(other, Employee):
return print("{0}的薪水为{1}, {2}的薪水为{3}, 两人薪水一共:{4}"
.format(self.name, self.__salary, other.name, other.__salary, self.__salary + other.__salary))
@property
def salary(self):
print("月薪为", self.__salary)
return self.__salary
@salary.sette
def salary(self, currentSalary):
if 1000 < currentSalary < 50000:
self.__salary = currentSalary
print("月薪设置成功")
else:
print("薪水录入错误! 只能在1000-50000之间")
e1 = Employee("时间静止", 5555)
e2 = Employee("静止时间", 4444)
Employee.__add__(e1, e2)
e1.salary = 4444
print(e1.salary)
e1.salary = 666
执行结果