Python系列文章目录
第一章 Python 入门
第二章 Python基本概念
第三章 序列
第四章 控制语句
第五章 函数
第六章 面向对象基础
第七章 面向对象深入
面向对象深入
前言
在本章我们首先学习了面向对象的三大特征, 继承, 封装和多态. 主要讲述了继承和多态:
继承某个父类, 可以实现并重写父类方法, 并且所有类都继承父类Object, 因此可以使用很多Object提供的特殊方法和特殊属性;
而多态指的的是不同子对象在调用父类方法时会有不同体现, 核心是: 继承和方法重写. 多态的出现也符合面向对象的"开闭原则"
封装之所以没讲是因为我们经常会用到, 因为在我们日常编程中, 将一些具有特定功能的代码块封装成方法/函数这种行为正体现了封装思想
然后学习了深拷贝和浅拷贝: 浅拷贝后对象的地址改变, 但子对象地址不改变. 深拷贝: 拷贝对象的地址改变, 子对象地址也改变
再然后我们了解编程中常用的工厂模式, 单例模式以及他们的组合, 最后通过实操来回顾下本章学习的内容
一、面向对象三大特征
Python是面向对象的语言
而面向对象编程具有三大特性:继承、封装(隐藏)、多态
具体含义如下
- 继承: 继承可以让子类具有父类的特性,提高了代码的重用性
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进已有的算法 - 封装(隐藏): 隐藏对象的属性和实现细节,只对外提供必要的方法
相当于将“细节封装起来”,只对外暴露“相关调用方法”
通过前面学习的“私有属性、私有方法”的方式,实现“封装”.
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() 代表父类的定义,不是父类对象
调用父类的构造方法: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 类是所有类的父类,因此所有的类都有 object 类的属性和方法
dir() 查看对象属性
而内置函数 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))
从上面代码在控制台的输出我们可以发现这样几个要点:
-
Person 对象相较于object, 增加了六个属性:
__dict__ , __module__ , __weakref__ , age , name , print_age
-
object 的所有属性, Person 类作为 object 的子类,显然包含了所有的属性
-
我们打印
age 、 name 、 print_age
,发现 print_age虽然是方法,实际上也是属性。只不过,这个属性的类型是 method 而已age <class 'int'> name <class 'str'> print_age <class 'method'>
mro() 获取类的层次结构
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)是指同一个方法调用由于对象不同可能会产生不同的行为
现实生活中,同一个方法具体实现会完全不同. 比如:同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭
注意:
- 多态是方法的多态,属性没有多态
- 多态的存在有2个必要条件:继承、方法重写
实操代码
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())
二、浅拷贝与深拷贝
- 浅拷贝: Python拷贝一般都是浅拷贝
浅拷贝:拷贝时,拷贝源对象,但对象包含的子对象内容不拷贝 - 深拷贝: 使用 copy 模块的 deepcopy 函数,递归拷贝对象中包含的子对象
深拷贝:拷贝时,拷贝源对象,也递归拷贝对象中包含的子对象
如图所示:
实操代码
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.setter 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
执行结果