Appearance
Python教程 - 7 面向对象(2)
继续讲解面向对象...
7.3 继承
在现实世界,有麻雀和鸽子,它们都属于鸟类,麻雀、鸽子和鸟类的关系是父类和子类的关系。
麻雀和鸽子都会飞,我们可以在麻雀类中定义一个飞的方法,在鸽子类中定义一个飞的方法,但是这两个飞的方法是一样的,都是用翅膀飞。我们可以在鸟类中定义一个飞的方法,让麻雀和鸽子类都继承这个鸟类,那么它们就拥有了飞的方法,就不用再定义了。
继承分为单继承和多继承:
单继承:一个类只继承一个父类
多继承:一个类继承多个父类
1 单继承
单继承的语法:
python
class 类名(父类名):
类内容麻雀继承鸟类,鸽子继承鸟类,都属于单继承,演示一下:
python
class Bird: # 定义一个鸟类
age = None # 鸟都有年龄
def fly(self): # 定义了一个飞的方法
print(f"我{self.age}岁了,我会飞")
def tweet(self): # 定义了一个叫的方法
print("我会叫")
class Sparrow(Bird): # 定义一个麻雀类,继承自鸟类
pass
class Pigeon(Bird): # 定义一个鸽子类,继承自鸟类
pass
sparrow = Sparrow() # 创建一个麻雀对象
sparrow.age = 1
sparrow.fly()
sparrow.tweet()
pigeon = Pigeon() # 创建一个鸽子对象
pigeon.age = 2
pigeon.fly()
pigeon.tweet()在上面的代码中,我们先定义了一个鸟类,然后在鸟类中定义了 age属性 ,并定义了两个方法 fly方法和 tweet方法;
然后定义类麻雀类,继承鸟类,那么就拥有了鸟类的属性和方法。鸽子类也同样。
执行结果:
我1岁了,我会飞
我会叫
我2岁了,我会飞
我会叫注意,继承不能继承父类私有的成员变量和方法。
如果父类的成员变量和属性不想被子类继承,可以设置为私有成员。
2 复写父类方法
所以继承父类就拥有了父类非私有的成员变量和方法,如果父类不是我想要的方法,我还可以进行覆盖。
例如,鸟类提供了叫的方法,但是我麻雀有自己的叫法,我要啾啾叫,那么我们可以重写父类的方法,实现自己的个性化。
python
class Bird: # 定义一个鸟类
age = None # 鸟都有年龄
def fly(self): # 定义了一个飞的方法
print(f"我{self.age}岁了,我会飞")
def tweet(self): # 定义了一个叫的方法
print("我会叫")
class Sparrow(Bird): # 定义一个麻雀类,继承自鸟类
pass
class Pigeon(Bird): # 定义一个鸽子类,继承自鸟类
def tweet(self): # 重写了父类叫的方法
print("我会咕咕叫")
sparrow = Sparrow() # 创建一个麻雀对象
sparrow.age = 1
sparrow.fly()
sparrow.tweet()
pigeon = Pigeon() # 创建一个鸽子对象
pigeon.age = 2
pigeon.fly()
pigeon.tweet()执行结果:
我1岁了,我会飞
我会叫
我2岁了,我会飞
我会咕咕叫3 调用父类方法
我们在重写父类方法的时候,应该尽量做到在父类方法的基础上进行扩展。
所以很有可能需要调用父类被重写的方法,然后在父类原来的方法上进行扩展。
那么如果我们想要在子类中调用父类被重写的属性和方法呢?
有两种方式:
方式一:
python
super().成员变量
super().成员方法方式二:
python
父类名.成员变量
父类名.成员方法(self)举个栗子:
python
class Bird: # 定义一个鸟类
age = 1
def tweet(self): # 定义了一个叫的方法
print("我会啾啾叫")
class Pigeon(Bird): # 定义一个鸽子类,继承自鸟类
age = 3
def tweet(self): # 重写了父类叫的方法
print("我会咕咕叫")
def test(self):
print(f"age:{Bird.age}") # 调用父类的成员
Bird.tweet(self)
print(f"age:{super().age}") # 调用父类的成员
super().tweet()
print(f"age:{self.age}")
self.tweet()
pigeon = Pigeon() # 创建一个鸽子对象
pigeon.age = 10
pigeon.test()执行结果:
age:1
我会啾啾叫
age:1
我会啾啾叫
age:10
我会咕咕叫4 继承的构造方法
子类继承父类,如果子类没有构造方法,则会使用父类的。
举个栗子:
python
class Bird:
age = None
def __init__(self, age):
self.age = age
class Pigeon(Bird):
pass
pigeon = Pigeon() # 创建对象,没有传入参数
print(pigeon.age)上面创建对象,没有传入参数,所以在运行的时候报错,因为没有构造方法,会使用父类的,父类的构造方法是需要传入参数的。
子类若有构造方法,则子类的构造方法必须先调用父类的构造函数。
python
class Bird:
age = None
def __init__(self, age):
self.age = age
class Pigeon(Bird):
def __init__(self, name, age):
super().__init__(age) # 调用父类的构造方法
self.name = name
pigeon = Pigeon("鸽子", 3)
print(pigeon.age)
print(pigeon.name)推荐上面这么做,即使你在子类的构造方法中可以这么写:
python
def __init__(self, name, age):
self.age = age
self.name = name这样代码 self.age = age 和父类中的重复了,不推荐。
5 类型判断
我们可以判断某个对象的类型,以及是否是某个对象的实例。
首先有如下代码:
python
# 麻雀类和鸽子类都继承自鸟类。
class Bird: # 鸟类
pass
class Sparrow(Bird): # 麻雀类,继承自鸟类
pass
class Pigeon(Bird): # 鸽子类,继承自鸟类
passtype() 函数
type()函数可以判断某个对象是否是某个类型,注意:子类的对象不是父类的对象的类型。
举个栗子,基于上面的代码:
python
pigeon = Pigeon()
sparrow = Sparrow()
print(type(pigeon) == Pigeon) # True,鸽子对象的类型是鸽子
print(type(pigeon) == Bird) # False,鸽子对象的类型不是鸟isinstance()函数
isinstance()函数可以判断某个对象是否是某个类型的实例,子类对象也是父类对象的实例。
举个例子,基于上面的代码:
python
pigeon = Pigeon()
sparrow = Sparrow()
print(isinstance(pigeon, Pigeon)) # True,鸽子对象是鸽子类的实例
print(isinstance(pigeon, Bird)) # True,鸽子对象是鸟类的实例
print(isinstance(sparrow, Pigeon)) # False,鸽子对象不是麻雀类的实例issubclass()函数
issubclass()函数可以用来判断一个类是否是另外一个类的子类。
举个栗子,基于上面的代码:
python
pigeon = Pigeon()
sparrow = Sparrow()
print(issubclass(Pigeon, Bird)) # True,鸽子类是鸟类的子类
print(issubclass(Pigeon, Sparrow)) # False,鸽子类不是麻雀类的子类6 多继承
我现在是一只鸡,我也想继承鸟类,但是我是家禽,我还想继承家禽类。
那么就需要用到多继承。
多继承的语法:
python
class 类名(父类1, 父类2, ..., 父类N):
类内容举个栗子:
python
class Bird: # 定义一个鸟类
age = None # 鸟都有年龄
def fly(self): # 定义了一个飞的方法
print(f"我{self.age}岁了,我会飞")
class Poultry: # 定义一个家禽类
number = None # 家禽需要编号
def eat(self): # 定义了一个吃的方法
print("我吃饭啦")
class Chicken(Bird, Poultry): # 定义一个鸡类,继承自鸟类和家禽类
def fly(self):
print("我不会飞")
chicken = Chicken() # 创建一个鸡对象
chicken.age = 1
chicken.number = 9527
chicken.fly()
chicken.eat()定义了一个鸡类,继承鸟类和家禽类,那么这个鸡类就拥有了鸟类和家禽类的属性和方法。
执行结果:
我不会飞
我吃饭啦多继承的解析顺序
如果继承的多个父类拥有同样的属性和方法,那么会使用哪个父类的呢?
会使用先继承的父类的,后继承的无效,如果父类中都没有,则会去父类的父类寻找,一直向上找。
即使类没有写明继承其他类,但是所有的类都直接或间接继承自Object类。
演示一下:
python
class Bird: # 定义一个鸟类
age = 1
def eat(self):
print("我去觅食")
class Poultry: # 定义一个家禽类
number = None
age = 3
def eat(self):
print("我开饭啦")
class Chicken(Bird, Poultry): # 定义一个鸡类,继承自鸟类和家禽类
pass
chicken = Chicken() # 创建一个鸡对象
print(chicken.age)
chicken.eat()因为先继承鸟类,所以是继承鸟类的成员属性和方法。
执行结果:
1
我去觅食我们还可以通过类的mro()方法,查看父类方法的解析顺序:
例如类D继承类B、C,类B、C继承A。
python
class A:
def test(self):
print("A")
class B(A):
def test(self):
print("B")
class C(A):
def test(self):
print("C")
class D(B, C):
pass
d = D()
d.test()
print(D.mro())执行结果:
B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]通过mro()方法可以查到类方法的解析顺序,也就是先找D,再找B,然后找C,再找A,最终找到Object类。
如果此时想调用其他父类的函数呢?
可是使用 父类名.方法名(self, 参数),例如我们在D类中,添加一个方法来调用:
python
class D(B, C):
def fun(self):
self.test() # 默认调用的是B类的test()方法
C.test(self) # 调用C类的test()方法7.4 多态
1 什么是多态
多态就是多种状态,同一个类型的父类型对象,因为指向的是不同的子对象,而表现出的不同的状态。
所以多态是建立在继承的基础之上的。
举个栗子:
python
class Bird:
def tweet(self):
pass
class Sparrow(Bird):
def tweet(self):
print("我会啾啾叫")
class Pigeon(Bird):
def tweet(self):
print("我会咕咕叫")
def bird_tweet(bird: Bird):
bird.tweet()
sparrow = Sparrow() # 创建一个麻雀对象
pigeon = Pigeon() # 创建一个鸽子对象
bird_tweet(sparrow)
bird_tweet(pigeon)上面麻雀类和鸽子类都继承自鸟类,这里还定义了一个函数 bird_tweet,函数的参数是鸟对象,在函数中,调用鸟类的叫方法。
然后创建了一个麻雀对象和鸽子对象,调用 bird_tweet 函数,分别传入麻雀对象和鸽子对象。
执行结果:
我会啾啾叫
我会咕咕叫bird_tweet 函数接收的是鸟类对象,执行的都是tweet()方法,但是因为是不同的子类对象,却得到不同的结果。
以父类做定义声明,以子类做实际的工作,用于获取同一个行为的不同状态,这就是多态。
那也没看出多态有什么用啊。
多态的作用:
- 提高代码的维护性
- 提高代码的扩展性
- 把不同的子类对象当做父类来看待,可以屏蔽不同子类对象之间的差异,写出通用的代码,以适应需求的不断变化。
2 多态的使用
说了那么多,有点虚,举个栗子:
实现一个功能:学生做交通工具去某个地方,交通工具可能是汽车、飞机。
先定一个汽车类:
python
class Car:
def run(self, destination):
print(f"开车去->{destination}")再定义一个飞机类:
python
class Plane:
def fly(self, destination):
print(f"飞去->{destination}")然后定义一个学生类:
python
class Student:
def go_to(self, vehicle, destination):
if type(vehicle) == Car:
vehicle.run(destination)
elif type(vehicle) == Plane:
vehicle.fly(destination)学生类有一个go_to()方法,接收交通工具和目的地,然后在方法中判断交通工具的类型,然后调用交通工具的方法。
调用代码:
python
stu = Student()
car = Car()
plane = Plane()
stu.go_to(car, "北京")
stu.go_to(plane, "新疆")执行结果:
开车去->北京
飞去->新疆上面的代码可以实现功能,但是不易于扩展,如果我们现在增加一个交通工具火车,则还需要修改Student类的go_to()方法,针对新的交通工具来处理,因为学生类和汽车、飞机类直接存在依赖关系,耦合性高。

这样是违反了设计原则中的开闭原则,对扩展是开放的,对修改是封闭的,也就是允许在不改变它代码的前提下变更它的行为。
所以上面的代码扩展性就比较差了,那么怎么来优化代码,降低代码的耦合性呢?
这就需要用到多态了。
首先定义一个父类Vehicle(交通工具类),并定义一个transport()方法,都是交通工具,都是运输功能嘛。
然后让Car类和Plane类都继承这个父类,因为不同的子类运输方式不一样,所以子类需要重写父类的方法,实现自己的功能。
python
class Vehicle:
def transport(self, destination):
pass
class Car:
def transport(self, destination):
print(f"开车去->{destination}")
class Plane:
def transport(self, destination):
print(f"飞去->{destination}")然后修改学生类:
python
class Student:
def go_to(self, vehicle, destination):
vehicle.transport(destination)学生类只需要调用交通工具的运输功能就可以了。
调用的代码不变:
python
stu = Student()
car = Car()
plane = Plane()
stu.go_to(car, "北京")
stu.go_to(plane, "新疆")执行结果:
开车去->北京
飞去->新疆上面的代码使用了多态,学生类与各个交通工具子类已经不直接产生关系,遵从了设置原则中的依赖倒置原则(程序依赖于抽象接口,不要依赖于具体实现)。

此时如果新增一个火车的交通工具,不用再修改学生类的代码,代码耦合性大大降低。
3 抽象类
什么是抽象类?
含有抽象方法的类成为抽象类。
那什么是抽象方法?
抽象方法就是没有方法体,方法体为空的方法。
上面的Vehicle类就是一个抽象类。
python
class Vehicle:
def transport(self, destination):
pass抽象类有什么作用呢?
一般抽象类都是作为父类使用的,父类用来确定有哪些方法,相当于用来确定设计的标准,用于对子类的约束。
子类用来实现父类的标准,负责具体的实现,配合多态使用,获得不同的工作状态。
7.5 类与类的关系
类与类有以下几种关系:
泛化
子类与父类的关系,B类泛化A类,B类是A类的一种。
场景:B类继承A类。
关联
部分与整体的关系,A与B关联,B是A的一部分。
场景:在A类中包含B类型的属性。
依赖
合作关系,A类依赖B类,A的某些功能需要靠B类实现。
场景:B类型作为A类中方法的参数,并不是A的成员。
7.6 数据与JSON的转换
1 JSON 简介
什么是JSON?
JSON就是特定格式的字符串。我们可以将数据按照这个格式进行封装,然后可以在不同的语言和系统之间进行传送和交互。
JSON的2种格式:
格式一:
是一个对象格式的结构。{} 括起来,其中是属性。
json
{
key1:value1,
key2:value2,
...
}举个栗子,以下是一个学生信息的JSON格式,和Python中的字典格式是一样的:
json
{
"sid": "001",
"name": "zhangsan",
"age": 18
}格式二:
外部是一个列表格式的结构,内部是一个个的对象格式的数据。
json
[
{
key1:value1,
key2:value2
},
{
key3:value3,
key4:value4
}
]举个栗子,以下是一个学生信息的列表
python
[{
"sid": "S001",
"name": "zhangsan",
"age": 18
},
{
"sid": "S002",
"name": "lisi",
"age": 19
},
{
"sid": "S003",
"name": "wangwu",
"age": 17
}
]在Python中,字典和JSON数据可以无缝切换。
将字典转换为JSON数据
python
import json
stu_dict = {"sid": "001", "name": "zhangsan", "age": 18}
json_data = json.dumps(stu_dict, ensure_ascii=False) # 使用dumps()方法,将字典转换为JSON数据
print(json_data)
print(type(json_data)) # 类型为字符串执行结果:
{"sid": "001", "name": "zhangsan", "age": 18}
<class 'str'>如果有中文,需要添加参数ensure_ascii=False。
将JSON数据转换为字典
python
import json
json_data = '{"sid": "001", "name": "张三", "age": 18}'
stu_dict = json.loads(json_data) # 使用loads()方法,将JSON数据转换为字典
print(stu_dict["name"])执行结果:
张三2 对象转JSON
对象转换为JSON
python
import json # 导入json模块
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
stu = Student("001", "张三", 98)
stu_json = json.dumps(stu)
print(f"对象转换为json:{stu_json}")在运行的时候会报错:TypeError: Object of type Student is not JSON serializable ,这是因为没有提供序列化的方法。
我们需要先获取对象的字典格式的数据:
python
import json # 导入json模块
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
stu = Student("001", "张三", 98)
stu_json = json.dumps(stu.__dict__, ensure_ascii=False) # 获取对象的字典格式
print(stu_json)执行结果:
{"sid": "001", "name": "张三", "age": 98}上面的写法有一个缺点,就是没法针对对象的属性值做特殊的处理,例如我们下面在对象中添加了一个时间的属性,然后输出JSON的时候,想按照指定的格式输出时间,则就需要对时间属性进行处理,此时就需要添加一个方法,返回对象的字典结构数据,在方法中对时间进行处理。
python
import json # 导入json模块
import time # 导入time模块
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
self.time = time.time()
def to_json_dict(self): # 提供一个方法将对象转换为字典格式
return {
"sid": self.sid,
"name": self.name,
"age": self.age,
"time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.time))
}
stu = Student("001", "张三", 98)
stu_json = json.dumps(stu.to_json_dict(), ensure_ascii=False)
print(stu_json)执行结果:
{"sid": "001", "name": "张三", "age": 98, "time": "2023-03-28 21:43:54"}列表转换为JSON
python
import json # 导入json模块
import time
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
self.time = time.time()
def to_json_dict(self): # 提供一个方法将对象转换为字典格式
return {
"sid": self.sid,
"name": self.name,
"age": self.age,
"time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.time))
}
stu_list = [Student("001", "张三", 98), Student("002", "李四", 99)]
stu_list_json = json.dumps([stu.to_json_dict() for stu in stu_list], ensure_ascii=False)
print(stu_list_json)使用了 [stu.to_json_dict() for stu in stu_list] 列表推导式,将一个装着对象的列表转换为一个装着字典的列表。
执行结果:
[{"sid": "001", "name": "张三", "age": 98, "time": "2023-03-28 21:52:02"}, {"sid": "002", "name": "李四", "age": 99, "time": "2023-03-28 21:52:02"}]3 JSON转对象
JSON转换为对象
使用 json.loads() 方法将 JSON 字符串转换为对象。
python
import json
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
json_str = '{"sid": "001", "name": "张三", "age": 98}'
stu_dict = json.loads(json_str) # 首先将JSON字符串转换为字典
print(stu_dict["name"])
stu = Student(**stu_dict) # 然后通过双星号字典形参传递给构造方法,创建对象
print(stu.name)一般将JSON转换为字典就可以获取到数据了,如果有特殊的需求,再将字典转换为对象。
执行结果:
张三
张三JSON转换为列表
python
import json
class Student:
def __init__(self, sid, name, age):
self.sid = sid
self.name = name
self.age = age
json_str = '[{"sid": "001", "name": "张三", "age": 98}, {"sid": "001", "name": "李四", "age": 98}]'
stu_dict_list = json.loads(json_str) # JSON转换为列表,列表中是字典
for stu in stu_dict_list:
print(f"字典 -> sid:{stu['sid']}, name:{stu['name']}, age:{stu['age']}")
stu_list = [Student(**stu_dict) for stu_dict in stu_dict_list] # 可以通过字典创建对象
for stu in stu_list:
print(f"对象 -> sid:{stu.sid}, name:{stu.name}, age:{stu.age}")一般将JSON转换为字典就可以获取到数据了,如果有特殊的需求,再将字典转换为对象。
执行结果:
字典 -> sid:001, name:张三, age:98
字典 -> sid:001, name:李四, age:98
对象 -> sid:001, name:张三, age:98
对象 -> sid:001, name:李四, age:98