Skip to content

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):			# 鸽子类,继承自鸟类
    pass

type() 函数

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