Python 数据可视化

matplotlib

matplotlib是一个用于创建静态、动态和交互式可视化图形的 Python 库。它被广泛用于数据可视化,并且可以与多种操作系统和图形后端一起工作。

matplotlib 的主要组成部分是 pyplot,它是一个类似于 MATLAB 的绘图框架。pyplot 提供了一个 MATLAB 式的接口,可以隐式地创建图形和轴,使得绘图变得简单。

Pycharm中需要安装 matplotlib 包 pip install matplotlib

mpld3

mpld3 是一个 Python 库,优势在于它能够将静态的 Matplotlib 图形转换为具有交互性的 D3.js 图形,使得用户可以通过鼠标交互来探索和操作图形。

pip install mpld3

Spyder

Pycharm 中绘图可能会出现图片无法正确显示颜色等问题,建议可视化代码运行在 Spyder 中。

安装网址: Spyder:https://www.spyder-ide.org

  注:以下可视化代码截图,将会从 Spyder 中运行后截取。

制图步骤

① 安装 matplotlib

pip install matplotlib

② 导入 matplotlib.pyplot

import matplotlib.pyplot as pltfrom matplotlib import pyplot as plt

③ 准备数据

准备所需的可视化数据。

④ 绘制图形

使用plt中的函数来绘制您想要的图形。以下是一些常见图形的绘制方法:

线图

1
plt.plot(x, y)  # x和y是数据点

散点图

1
plt.scatter(x, y)  # x和y是数据点

条形图

1
plt.bar(x, y)  # x是分类变量,y是对应值

直方图

1
plt.hist(data, bins=10)  # data是要统计的数据,bins是分组数量

饼图

1
plt.pie(sizes, labels=labels)  # sizes是各部分大小,labels是各部分标签名

⑤ 定制图形

可以添加标题、轴标签、图例以及调整轴的范围等

1
2
3
4
5
6
7
8
plt.title("My Plot")  # 添加标题
plt.xlabel("X Axis") # 添加X轴标签
plt.ylabel("Y Axis") # 添加Y轴标签
plt.legend() # 添加图例
plt.xlim(0, 10) # 设置X轴范围
plt.ylim(0, 100) # 设置Y轴范围
plt.xticks(range(0, 10)) # 设置X轴刻度
plt.yticks(range(0, 10)) # 设置Y轴刻度

⑥ 显示或保存图形

最后,使用 plt.show() 来显示图形,或者使用 plt.savefig() 来保存图形到文件

1
2
3
plt.show()  # 显示图形
# 或者
plt.savefig('my_plot.png') # 保存图形为PNG文件

⑦ (可选)使用 subplots 创建多个子图

如果想在同一个窗口中显示多个图形,可以使用 plt.subplots()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig, axs = plt.subplots(2, 2)  # 创建一个2x2的子图网格,默认nrows = 1, ncols = 1

# 整个图形的标题
fig.suptitle('Plot of Sine Function')

# 然后可以在每个子图上进行绘制,例如:
axs[0, 0].plot(x1, y1) # 在第一个子图上绘图
axs[0, 1].scatter(x2, y2) # 在第二个子图上绘图
# ...以此类推

'''
fig:表示整个图形窗口的对象(Figure)。
它是你所有子图和其他绘图元素的容器。可以通过fig来设置整个图形的属性,例如标题、大小等。

axs:表示一个包含多个子图的数组(Axes)。
通过索引访问每个子图,例如axs[0, 0]表示第一行第一列的子图,axs[1, 1表示第二行第二列的子图。
'''

⑧ (可选)Pandas

Pandas 是基于 numpy 的一种工具,该工具是为解决数据分析任务而创建的,提供了大量能使我们快速便捷地处理数据的函数和方法,帮我们高效地操作大型数据集,可以通过 pandas 包读取文件中数据进行绘图。

安装 pip install pandas

绘图示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import matplotlib.pyplot as plt  # matplotlib.pyplot 是一个用于绘图的库,提供了一系列绘图函数。
import numpy as np # numpy 是一个用于科学计算的库,特别擅长处理数组和数学运算。

# 创建数据
x = np.linspace(0, 10, 100) # 生成一个包含 100 个均匀分布的数值的数组,这些数值在 0 到 10 之间。
y = np.sin(x) # 计算 x 中每个值的正弦,结果存储在 y 数组中。

# 绘制折线图
plt.plot(x, y, "-", label="sinx") # 绘制 x 和 y 之间的关系。

# 设置图表标题和坐标轴标签
# 慎用中文,很容易报错
plt.title("Simple Plot of Sine Function") # 设置图表的标题。
plt.xlabel("X-axis") # 设置 X 轴的标签。
plt.ylabel("Y-axis") # 设置 Y 轴的标签。
plt.legend(loc="upper right") # 图例需要搭配plt.plot()中的label标签使用。
# 可以使用plt.legend()函数中的loc参数修改图例的位置,upper right、lower left、center left、best等

# 显示图形
plt.show() # 显示图形窗口,呈现出绘制的折线图。

注意事项:

matplotlib 具有高度的可定制性,在绘制复杂图形或者绘制图形不满意时,可以通过 Chatgpt 等其他语言大模型的帮助,获得更精细的控制和更理想的图形。

每个函数里面都有许多参数,如 loc,lizestyle,labeldistance 等,都学习的成本太高了也记不住,在后续常用图形中会捎带讲解,只需了解和会读。

常用图形

线图(Line Plot)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt

# 创建数据
x = [1, 2, 3, 4, 5, 6, 7, 8]
y = [1, 3, 5, 0, 2, 3, 5, 8]

# 绘制线图
plt.plot(x, y, color="red", alpha=0.5, linestyle="--", linewidth=2, marker="o")
# color调整线条颜色,还可以用 color="#ffff00" 或 color="b" 这种格式
# alpha调整透明度,范围[0, 1]
# linestyle调整线条格式,即Linear Style,实线"-"、虚线"--"、点划线"-."、点虚线":"
# linewidth调整线条宽度,默认为1.5
# marker调整散点的形状,会在散点图一节中详细列出。

plt.title("Line Plot")
plt.xlabel("X Axis")
plt.ylabel("Y Axis")
# plt.grid(True) # 用于显示点状网格线,有助于辅助构图
plt.grid(color="green", alpha=0.5, linestyle=":", linewidth=1, axis="x")
# axis="x"表示显示x轴网格线,也可以填入x和both
plt.show()

散点图 (Scatter Plot)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from matplotlib import pyplot as plt
import numpy as np

# 创建数据
x = np.random.rand(50) # 生成一个1x50的随机数数组,也就是50个[0, 1)的数
y = np.random.rand(50)

# 绘制散点图
plt.scatter(x, y, marker="s") # marker调整散点或者折线转折处的形状
plt.title("Scatter Plot")
plt.xlabel("X Axis")
plt.ylabel("Y Axis")
plt.grid(True)
plt.show()
设置值 说明 设置值 说明
. s 实心正方形
, 像素 p 实心五角形
+ 加号 P 实心加号
x 叉号 X 实心叉号
d 菱形 D 粗菱形
o 实心圆 h 竖正六边形
* 星形 H 横实心正六边形
| 竖线 _ 短横
v 下三角 1 上花三角
^ 上三角 2 下花三角
< 左三角 3 左花三角
> 左三角 4 右花三角

条形图 (Bar Chart)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from matplotlib import pyplot as plt
import random

# 创建数据
categories = [1, 2, 3, 4, 5]
values = [random.randint(10, 60) for i in range(5)]
# 设置字体为支持中文的字体,比如'SimHei'(黑体),确保你的系统中安装了该字体
plt.rcParams["font.sans-serif"] = ["SimHei"] # 用于正常显示中文标签
plt.rcParams["axes.unicode_minus"] = False # 用来正常显示负号
# 一般这两句都是一起出现的。

for a, b in zip(categories, values):
plt.text(a, b + 1, b, ha="center")
"""
plt.text(x, y, s, fontsize, verticalalignment, horizontalalignment, color, **kwargs)
x:标签横向坐标
y:标签纵向坐标
s:标签的内容,字符串格式,
fontsize:标签字体大小
verticalalignment(va):垂直对齐方式,可以选center、top、bottom、baseline等
horizontalalignment(ha):水平对齐方式,可以选center、right、left等
color:注释文本内容的字体颜色
"""

# 绘制条形图
plt.bar(categories, values, width=0.5)
plt.title("Bar Chart")
plt.xlabel("Categories")
plt.ylabel("Values")
# plt.legend("测试图例") # 除了在bar中添加label也可直接在legend()中写图例名称
plt.legend(("测试图例",)) # 但是直接写的方式可能会出现显示不全,用这一行的技巧可以轻松解决
plt.show()

直方图 (Histogram)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
from matplotlib import pyplot as plt

# 创建正态分布的随机数据(均值为0,标准差为1)
data = np.random.randn(100) # 生成100个正态分布的随机数
# 函数是一个非常重要的工具,用于生成满足标准正态分布(均值为0,标准差为1)的随机数或随机数组。
# 这个函数在数据分析、机器学习、模拟实验等领域都有着广泛的应用。
print(data)

# 将数据排序
data = np.sort(data)
print(data)

# 绘制直方图
plt.hist(data, bins=20, edgecolor="black", color="skyblue", alpha=0.7)
# x:也就是给的data数据集,最终的直方图将对数据集进行统计
# bins:统计数据的区间分布,表示将数据分成 20 组

# 定制图形
plt.title("Histogram of Normal Distribution")
plt.xlabel("Value Range")
plt.ylabel("Frequency")
plt.grid(axis="y", linestyle="--")

# 显示图形
plt.show()

饼图 (Pie Chart)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from matplotlib import pyplot as plt

# 创建数据
labels = ["A", "B", "C", "D"]
sizes = [15, 30, 45, 10]
# plt.figure(figsize=(8, 3), dpi=200)
# figure要写在所有plt之前,表示铺一张空白画布,dpi默认值为100
# figsize:画布大小
# dpi:图片分辨率

# 绘制饼图
plt.pie(sizes, labels=labels, labeldistance=1.08, autopct="%1.1f%%", startangle=90)
# x:也就是括号中的sizes,表示每一块饼形图的比例
# labels:每一块饼图外侧显示的说明文字
# labeldistance:labels的显示位置,相对于半径的比例,默认值为1.1
# autopct:设置饼图百分比,可以用格式化字符串(在基础篇有字符串格式化详解)
# startangle:指定起始角度,正东方向为0度
# radius:饼图半径,默认值为1

plt.axis("equal") # 让x轴y轴相等,以保证是圆形,出现其他形状的时候写上这一行。
plt.title("Pie Chart")
plt.legend(labels, loc="upper right")

plt.show()

注:还有一些图形有特定的使用场景,需要的时候再进行具体学习。

  • 直方图 2d(Hist2d Plot)
  • 面积图(Area Plot)
  • 热力图(Heatmap)
  • 三维图形(3D Plot)
  • 堆叠图(Stacked Plot)

Python 面向对象

面向过程:把完成某一个需求的所有步骤从头到尾逐步实现,根据开发需求,将某些功能独立的代码封装成一个又一个函数,最后完成的代码,就是顺序地调用不同的函数

面向对象:在完成某一个需求前,首先确定要做的事情,根据事情确定不同的对象,在对象内部封装不同的方法,最后完成的代码,就是顺序地让不同的对象调用不同的方法

  注:在类内叫方法类外叫函数,其实是差不多的东西,就相差一个self(稍后介绍)。

总结:面向过程更像是一步一步地做事,面向对象则像是把事物看作盒子,每个盒子里有自己所需的数据和功能。

面向对象的三大特征:封装、继承和多态

类和对象

定义

是对一群具有相同特征或者行为的事务的一个统称,是抽象的,不能直接使用

  • 在类中 特征 被称为 成员变量(属性)

  • 在类中 行为 被称为 成员方法

就相当于制造飞机时的图纸,是一个模板,是负责创建对象的

类名的命名规则需要满足大驼峰命名法,例 MyClass

  1. 每一个单词的首字母大写

  2. 单词与单词之间没有下划线

对象

对象由类创建出来的一个具体存在,可以直接使用

哪一个类创建出来的对象,就拥有在哪一个类中定义的:属性和方法

类实例化一个对象就相当于根据某张图纸制造一架飞机

一张图纸可以造出许多架飞机,同理,一个类也能实例化出多个对象

语法

1
2
3
4
5
6
7
8
9
10
11
class Student:
name = None # 成员变量(属性)
age = None

def say_hi(self, msg): # 成员方法,第一个参数永远是self
print(f"旅行者你好!我的名字叫{self.name},今年{self.age}岁,是蒙德城连续三年的「飞行冠军」!\n{msg}")

stu_1 = Student()
stu_1.name = "安柏"
stu_1.age = 16
stu_1.say_hi("欢迎来到蒙德城!")
  • 类的外部,通过 变量名. 访问对象的 属性和方法
  • 类封装的方法中,通过 self. 访问对象的 属性和方法

魔术方法

在 Python 中,魔术方法(magic methods),是指以双下划线开头和结尾的特殊方法。

总览

方法 功能
__init__ 构造方法,可用于创建类对象的时候设置初始化行为
__str__ 用于实现类对象转字符串的行为
__lt__ 用于 2 个类对象进行小于或大于比较
__le__ 用于 2 个类对象进行小于等于或大于等于比较
__eq__ 用于 2 个类对象进行相等比较
__init__ (构造方法)

作用:为对象的属性设置初始值,该方法在创建对象时会被自动调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
name = None
age = None
tel = None # 这三行可以省略

def __init__(self, name, age, tel): # 构造方法也是方法,所有不能漏掉 self
self.name = name # 这一行会自动为对象stu生成一个name属性,并将传入的参数值赋给它。
self.age = age # 因此,不需要在类体中先定义 name = None等
self.tel = tel
print("Student类创建了一个类对象")


stu = Student("迪卢克", 25, "12367896789")
print(stu.name, stu.age, stu.tel)

构造方法和手动赋值的区别:

  • 对象在创建时就保证了必要的属性被赋值,避免对象处于不完整或错误的状态。

  • 代码简洁,不需要额外的赋值步骤。

  • 可以封装复杂的初始化逻辑,避免手动赋值时遗漏或导致错误。

  • 增强了属性的一致性和对象的不可变性,防止成员变量被随意修改。

有了构造方法后,每次实例化 Student 类时,你必须传递 nameagetel 这三个参数,如果你想在实例化时可以不传递参数,有几种常见的做法:

1. 为参数设置默认值

1
2
3
4
5
6
7
8
9
10
11
class Student:

def __init__(self, name="旅行者", age=17, tel="12356785678"): # 这里也要遵守缺省函数的规则
self.name = name
self.age = age
self.tel = tel
print("Student类创建了一个类对象")


stu = Student()
print(stu.name, stu.age, stu.tel)

  缺省函数规则: 一旦某个键设了默认值,那么从这个键往后的键都必须设置默认参数。

2. 使用可变参数

1
2
3
4
5
6
7
8
9
10
11
class Student:

def __init__(self, **kwargs):
self.name = kwargs.get("name", None)
self.age = kwargs.get("age", None)
self.tel = kwargs.get("tel", None)
print("Student类创建了一个类对象")


stu = Student(name="凯亚")
print(stu.name, stu.age, stu.tel)

kwargs:是一个可变长度的关键字参数字典,包含传递给函数的所有关键字参数。在函数调用时,所有以 key=value 形式传递的参数会存储在这个字典中。

get("name", None)get() 是 Python 字典类型的一个方法。它的作用是:

  • 从字典中获取指定键 "name" 的值。
  • 如果键 "name" 存在,就返回对应的值。
  • 如果键 "name" 不存在,则返回你指定的默认值,这里是 None
__str__

默认情况下,直接打印实例化对象会输出这个变量所引用的对象是由哪一个类创建的对象,以及在内存中的地址,用 __str__ 这个内置方法能够打印 自定义的内容

__del__

创建对象时,会给对象分配一片空间存储,接着会自动调用 __init__ 方法进行构造,那如果想删除一个对象,我们可以使用del关键字,语法:del 对象名

  注意:此时我们删除掉的是从命名空间中对删除对象的引用,而不是直接删除对象本身,如果还有其他引用指向该对象,则对象不会被删除,直到所有引用都被删除,对象才会真正被删除。

如果我们在类中创建了__del__ 方法,那么销毁的时候会自动调用__del__ 方法,所以如果希望在对象被销毁前,再做一些事情,比如关闭文件、释放网络连接等,可以使用 __del__ 方法。

生命周期

  • 一个对象从调用 类名() 创建,生命周期开始
  • 一个对象的 __del__ 方法一旦被调用,生命周期结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"炼金术施展成功!{self.name}已成功被初始化~")

# __str__魔术方法
def __str__(self):
# 如果不写__str__方法,直接打印实例化对象,只会打印出对象的地址。
return f"Student类对象,name:{self.name}, age:{self.age}"

# __del__魔术方法
def __del__(self):
print(f"啊!我{self.name}要被销毁了~")
# 销毁前可以再做一些事,它的主要功能是用于资源回收,比如关闭文件、释放网络连接等。
# 通过实现__del__方法,可以确保在对象生命周期结束时,自动执行一些清理工作,避免资源泄露。

stu = Student("阿贝多", 24)
print(stu)
del stu
# print(stu) # 无法再次输出。
运算符重载

运算符重载‌ 是指赋予基本运算符新的运算功能,使其能够应用于自定义类型的运算。

总览

魔法方法 说明
__add__(self, other) 加法运算 +
__sub__(self, other) 减法运算 -
__mul__(self, other) 乘法运算 *
__floordiv__(self, other) 浮点除法运算 //
__mod__(self, other) 取余运算 %
__pow__(self, other[, module])) 指数运算 **
__and__(self, other) 按位与运算 &
__xor__(self, other) 按位异或运算 ^
__or__(self, other) 按位或运算 |
__lt__(self, other) 比较操作符 <
__gt__(self, other) 比较操作符 >
__le__(self, other) 比较操作符 <=
__ge__(self, other) 比较操作符 >=
__eq__(self, other) 比较操作符 ==
__ne__(self, other) 比较操作符 !=
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

# __lt__魔术方法 # less than
def __lt__(self, other):
return self.age < other.age

# __le__魔术方法 # less equal
def __le__(self, other):
return self.age <= other.age

# __eq__魔术方法 # equal
def __eq__(self, other):
return self.age == other.age


stu1 = Student("神里绫华", 19)
stu2 = Student("神里绫人", 23)
print(stu1)
print(stu1 == stu2)

封装

定义

封装是将对象的变量(属性)和行为(方法)打包在一起,隐藏对象的内部实现细节,只提供公开的接口(或方法)与外部交互

好处:

  • 隐藏实现细节,提供清晰的对外接口,减少错误易于代码维护

  • 可以对内部属性进行验证,保证数据的一致性和准确性

  • 封装提高了代码的内聚性易于模块化和重用

两步走:

  • 属性方法 封装到一个抽象的

  • 在类外 使用 创建 对象,然后让对象调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Gun:

def __init__(self, model):
# 枪的型号
self.model = model
# 子弹数量
self.bullet_count = 0

def add_bullet(self, count):
self.bullet_count += count

def shoot(self):
# 判断是否还有子弹
if self.bullet_count <= 0:
print("没有子弹了...")

return

# 发射一颗子弹
self.bullet_count -= 1

print(f"{self.model}发射子弹~ [还剩{self.bullet_count}颗]...")


class Soldier:

def __init__(self, name):

# 姓名
self.name = name
# 枪,士兵初始没有枪 None 关键字表示什么都没有
self.gun = None

def fire(self):

# 1. 判断士兵是否有枪
if self.gun is None:
print(f"{self.name}还没有枪~")

return

# 2. 高喊口号
print(f"冲啊{self.name}!")

# 3. 让枪装填子弹
self.gun.add_bullet(50)

# 4. 让枪循环发射子弹
for i in range(10):
self.gun.shoot()


# 创建一个枪对象进行测试
ak47 = Gun("ak47")
ak47.add_bullet(50)
ak47.shoot()

print("-------------------")

# 创建一个士兵对象
xu3duo = Soldier("xu3duo")
xu3duo.gun = Gun("M416")
# Python的数据类型是灵活可变的,这里就把Gun类看成一种数据类型传给变量gun,好似int型、str型等
xu3duo.fire()

私有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Girl:

def __init__(self, name):
self.name = name
self.__age = 18
self.__weight = 125
# 科普:在C和Java中,都是用private、public和protected三个关键字。

def __weight(self):
print(f"{self.name}的体重是{self.__weight}kg。")

def scales(self): # 哈哈,我是体重秤
print(f"{self.name}的年龄是{self.__age},体重是{self.__weight}kg。")
# 不管对外有多私有,在类内都是可以直接访问的,相当于这个对象本人肯定是知道自己的属性的。


girl = Girl("小菲")
# 私有属性,外部不能直接访问
print(girl.name) # 小菲
# print(girl.__age) # 属性错误 AttributeError: 'Girl' object has no attribute '__age'

girl.__age = 17
print(girl.__age) # 17
"""
Q:当我们尝试给私有变量赋值的时候,会发现不报错,打印时,输出也会正常输出,这是为什么呢?
A:当你在外部执行 girl.__age = 17 时,Python并没有修改类中原来的 self.__age,
而是给对象 girl 添加了一个新的公共属性 __age,与原本私有的 __age 并无关系。
这就是为什么你可以通过 print(girl.__age) 输出结果。
"""
print(girl._Girl__age) # 18
"""
在Python中,并没有真正意义的私有,私有属性和方法在内部会被 Python 自动改名为 _ClassName__attribute 的形式,
以避免外部直接访问。例如,在 Girl 类中,self.__age 实际上会被改写为 self._Girl__age。
所以我们可以通过 print(girl._Girl__age) 正常打印出我们在类内的赋值 18,
但是,在日常开发中,不要使用这种方式,访问对象的 私有属性 或 私有方法。
"""

# 私有方法,外部不能直接调用
# girl.__weight() # AttributeError: 'Girl' object has no attribute '__weight'
girl.scales() # 小菲的年龄是18,体重是125kg。

继承

引例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class iPhone6:
IMEI = None # 序列号
producer = None # 厂商

def call_by_3g(self):
print("3g通话")


class iPhone16ProMax(iPhone6):
face_id = True # 面部识别

def call_by_5g(self):
print("2024最新款")


i16pm = iPhone16ProMax()
i16pm.IMEI = "10001"
i16pm.producer = "华强北"
print(i16pm.IMEI)
print(i16pm.producer)
print(i16pm.face_id)
i16pm.call_by_3g()
i16pm.call_by_5g()

定义

继承是指在一个类中可以使用另一个类中,已有的成员变量和成员方法,这样可以减少代码的重复性方便代码的维护和更新

  我们称继承类为派生类或子类(Derived class、Subclass),被继承的类称为基类、父类或超类(Base class、Parent class、Super class)

  子类可以继承父类所有的成员变量和成员方法,私有成员同样被继承,可以通过 _ClassName__attribute 的形式访问,但是不建议,所以我们默认子类继承了父类中的公共成员变量和公共成员方法,即非私有成员

单继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Animal:
def __init__(self, name):
self.name = name

def eat(self):
return "eat"

def drink(self):
return "drink"

def sleep(self):
return "sleep"

def run(self):
return "run"


class Dog(Animal):
def bark(self):
return "bark"


class Cat(Animal):
def meow(self):
return "meow"


dog = Dog("Dundun")
print(f"My name is {dog.name}, I can {dog.eat()}{dog.drink()}{dog.sleep()}{dog.run()} and {dog.bark()}.")

cat = Cat("Mimi")
actions = f"{cat.eat()}{cat.drink()}{cat.sleep()}{cat.run()} and {cat.meow()}"
print(f"My name is {cat.name}, I also can {actions}.")

传递性

子类 拥有 父类 以及 父类的父类 中封装的所有 属性方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Animal:
def __init__(self, name):
self.name = name

def eat(self):
return "eat"

def drink(self):
return "drink"

def sleep(self):
return "sleep"

def run(self):
return "run"


class Dog(Animal):
def bark(self):
return "bark"


class Xiaotiandog(Dog):
def fly(self):
return "fly"


xtd = Xiaotiandog("白犬神嗷")
actions = f"{xtd.eat()}{xtd.drink()}{xtd.sleep()}{xtd.run()} and {xtd.bark()}"
print(f"My name is {xtd.name}, not Only Can I {actions}, but I can also {xtd.fly()}!")

多继承

子类 可以拥有 多个父类,并且具有 所有父类属性方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Animal:
def __init__(self, name):
self.name = name

def eat(self):
return "eat"

def drink(self):
return "drink"

def sleep(self):
return "sleep"

def run(self):
return "run"


class cattle(Animal): # 牛
def moo(self):
return "哞哞"


class horse(Animal): # 马
def neigh(self):
return "嘶嘶"


class Niuma(cattle, horse): # 牛马
def ok(self):
return "收到!"


nm = Niuma("牛马")
print("牛会哞马会叫,牛马会收到!")
print(nm.moo()) # 小牛马 可以使用 牛 的成员方法 moo
print(nm.neigh()) # 小牛马 可以使用 马 的成员方法 neigh
print(nm.ok()) # 当然了,如果小牛马的父类还有成员变量,也可以照用无误

多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)优先级

即:先继承的保留,后继承的被覆盖

  提示:在开发中,如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class A:
def show(self):
print("A")


class B(A):
def show(self):
print("B")


class C(A):
def show(self):
print("C")


# class D(C, B):
class D(B, C): # 如果有同名成员,先继承谁,就会保谁的内容
pass # pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思


"""
Q:class D(A, B, C)或者 class D(A, C, B)的写法是不被允许的,这是为什么?
A:Python 的多重继承中,类的继承顺序要遵守 MRO(Method Resolution Order方法解析顺序)的规则,
MRO使用C3线性化算法,C3算法的核心是merge()函数,这个函数的功能是遍历继承的父类,将每个父类看成一个序列,
如果一个序列的第一个元素,是其他序列中的第一个元素,或不在其他序列出现,就将其排到新列表中,
以这个class D(A, B, C)为例,我们有,序列1 [A]、序列2 [B, A](因为B继承了A)、序列3 [C, A]。
新列表中第一个应该是本身D,接着应该先放入B,因为A在其他序列出现,且不是在其他序列的第一个,其次放入C,最后放入A,
但是现在class D(A, B, C)排在第一个的A类,并不能符合MRO的规则,产生了冲突,所以就报错了。
"""

d = D()
d.show()
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

__mro__

  • 在搜索方法时,是按照 __mro__ 的输出结果 从左至右 的顺序查找的,其中objectPython 为所有对象提供的 基类
  • 如果在当前类中 找到方法,就直接执行,不再搜索
  • 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到方法,程序报错

所以,慎用多继承。

复写(重写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Animal:
id = "Animal"
use_tools = False
breathe = True
feel_world = True

def eat(self):
print("吃东西")

def drink(self):
return "山泉水"


class Rabbit(Animal):
id = "Rabbit"

def eat(self):
return "吃胡萝卜"

def drink(self):
print(f"我要喝魔法兔子水,我的父类会喝{super().drink()}。")
print(f"我要喝魔法兔子水,我的父类会喝{Animal.drink(self)}。")


class Person(Animal):
id = "Person"
use_tools = True

def eat(self):
return "吃预制菜"

def id_show(self):
print(f"我的身份是 {self.id}")
print(f"我的父类是 {super().id}")
print(f"我的父类是 {Animal.id}")


r = Rabbit()
print(f"兔子{r.eat()},会感受世界:{r.feel_world},还会使用工具:{r.use_tools}")
r.drink()
print("----------------------------------------------")
p = Person()
print(f"人会{p.eat()},会感受世界:{p.feel_world},还会使用工具:{p.use_tools}")
p.id_show()

类型注解

定义

类型注解(Type Annotations)是 Python 的一种语法,用于在代码中标注变量、函数参数和返回值的类型。这可以帮助开发者理解代码的意图,并且在一些开发工具和静态分析工具中提高代码的可读性和可维护性。类型注解在 Python 3.5 引入,并在 Python 3.6 之后得到了进一步的完善。

变量注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json
import random

var_1 = random.randint(1, 10) # type: int
var_2 = json.loads('{"name": "LittleSugar"}') # type: dict[str, str]
# JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,后续会详解。
# 通过json.loads(data)方法把json数据转化为了python数据,析出了dict字典类型。

def func():
return 10
var_3 = func() # type: int

var_4: int = "SpiderMan"
var_5: str = 123
print(var_4, var_5)
# 即使标注错了,也不影响程序正确运行

  注意:一般在无法直接看出变量类型的时候,才会添加变量的类型注解,那种一眼就能看出来的显式类型一般都不用标注。

  快捷键:把光标移到括号内,按 Ctrl + P 查看注解提示

函数注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 对形参进行类型注解
def add(x: int, y: int):
return x + y

a = add("a", "dd")
print(a)


# 对返回值进行类型注解
def func(data: list) -> list:
return data

my_list = func(["a", "b"])
my_list.append("c")
print(my_list)

Union 联合类型注解

1
2
3
4
5
6
7
8
9
from typing import Union

my_list: list[Union[int, str]] = [1, 2, "itmimi", "itcat"]


def func(data: Union[int, str, list]) -> Union[int, str, list]:
return type(data)

print(func(my_list))

多态

定义

多态是指在不同的类中实现相同的方法,而方法的行为会根据对象的类型不同而有所不同。它强调的是同一个接口,不同的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Animal:
def speak(self):
pass


class Dog(Animal):
def speak(self):
print("汪汪汪")


class Cat(Animal):
def speak(self):
print("喵喵喵")


def make_sound(animal: Animal): # 需要传入Animal类或者其子类的对象
animal.speak()


# 实例化两个子对象
dog = Dog()
cat = Cat()

# 将对象传入函数,因为Dog和Cat都继承了Animal,
# 所以Animal拥有的成员内容,子类也有,所以传入子类对象也是没问题的
make_sound(dog)
make_sound(cat)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 演示抽象类
class AC:
def cool_wind(self):
"""制冷"""
pass

def hot_wind(self):
"""制热"""
pass

def swing_l_r(self):
"""左右摆风"""
pass


class MideaAC(AC): # 命名类名最好符合大驼峰命名方式,即MyClass这种形式
def cool_wind(self):
print("美的空调制冷")

def hot_wind(self):
print("美的空调制热")

def swing_l_r(self):
print("美的空调左右摆风")


class GreeAC(AC):
def cool_wind(self):
print("格力空调制冷")

def hot_wind(self):
print("格力空调制热")

def swing_l_r(self):
print("格力空调左右摆风")


def use_ac(ac: AC):
ac.cool_wind()
ac.swing_l_r()


midea_ac = MideaAC()
gree_ac = GreeAC()


use_ac(midea_ac)
use_ac(gree_ac)

区别

  • 多态 关注的是不同类之间,通过相同接口表现不同的行为,在 Python 中常通过继承和复写来实现动态多态。
  • 复写 是子类重写父类的方法,改变父类已有的行为,子类可以通过 super() 来调用父类的版本,复写是实现多态的一种方式。

 简言之,复写是实现多态的手段之一,而多态是一种通过接口统一实现不同行为的能力。

Python 数据分析案例

JSON

JSON(JavaScript Object Notation,JavaScript 对象表示法)

  优点:

  Python 语言使用 JSON 有很大优势,因为:JSON 无非就是一个单独的字典或一个内部元素都是字典的列表

  所以 JSON 可以直接和 Python 的字典或列表 进行无缝转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import json

# 准备列表,列表内每一个元素都是字典,将其转换为JSON
data = [
{"name": "张大山", "age": 11},
{"name": "王大锤", "age": 13},
{"name": "赵小虎", "age": 16},
]

json_str = json.dumps(data, ensure_ascii=False)
"""
当 ensure_ascii 设置为 True(默认值)时,输出会确保将所有输入的非ASCII字符转义。
换句话说,它会将所有无法用ASCII表示的字符转换成对应的Unicode编码。
这样,经过 dumps 后的字符串中,汉字会变成以 \u 开头的Unicode编码.
当 ensure_ascii 设置为 False 时,这些字符会原样输出。
这意味着,如果你的数据中包含汉字,你应该设置 ensure_ascii=False,以便保留汉字而不是转义成Unicode编码。
"""
print(type(json_str))
print(json_str)

# 准备字典,将字典转换为JSON
d = {"name": "周杰伦", "addr": "台北"}
json_str = json.dumps(d, ensure_ascii=False)
print(type(json_str))
print(json_str)

# 将JSON字符串转换为Python数据类型[{k: v, k: v}, {k: v, k: v}]
s = '[{"name": "张大山", "age": 11}, {"name": "王大锤", "age": 13}, {"name": "赵小虎", "age": 16}]'
l = json.loads(s)
print(type(l))
print(l)

# 将JSON字符串转换为Python数据类型{k: v, k: v}
s = '{"name": "周杰伦", "addr": "台北"}'
d = json.loads(s)
print(type(d))
print(d)

案例

data_define.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""
定义和数据相关的类
"""

class Record: # 记录类,表示一条条的记录

def __init__(self, date, order_id, money, province):
self.date = date # 订单日期
self.order_id = order_id # 订单ID
self.money = money # 订单金额
self.province = province # 销售省份
# 鼠标中键往下拖可以一下子选多行,就像植物大战僵尸里『你看,他们像柱子一样』那一关。

def __str__(self): # 用 __str__ 这个内置方法能够打印自定义的内容。
return f"{self.date}, {self.order_id}, {self.money}, {self.province}"

file_define.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
"""
定义和文件相关的类
"""

import json

from data_define import Record


# 先定义一个抽象类用来做顶层设计,确定有哪些功能需要实现
class FileReader:

def read_data(self) -> list[Record]: # 读取的返回值类型应该都是记录类的类型
"""读取文件的数据,读到的每一条数据都转换为Record对象,将它们都封装到list内返回即可"""
pass


class TextFileReader(FileReader):

def __init__(self, path):
self.path = path # 定义成员变量记录文件的路径

# 复写(实现抽象方法)父类的方法
def read_data(self) -> list[Record]:
f = open(self.path, "r", encoding="UTF-8")

record_list: list[Record] = []
for line in f.readlines():
line = line.strip() # 消除读取到的每一行数据中的\n
# 字符串.strip()移除首尾的空格和换行符,字符串.strip(字符串)移除首尾的指定字符串
data_list = line.split(",")
record = Record(data_list[0], data_list[1], int(data_list[2]), data_list[3])
record_list.append(record)

f.close()
return record_list


class JsonFileReader(FileReader):

def __init__(self, path):
self.path = path # 定义成员变量记录文件的路径

def read_data(self) -> list[Record]:
f = open(self.path, "r", encoding="UTF-8")

record_list: list[Record] = []
for line in f.readlines():
data_dict = json.loads(line)
record = Record(
data_dict["date"], # 字典[Key],获取指定Key对应的Value值
data_dict["order_id"],
data_dict["money"],
# 数据文件中key="money"对应的value的值的类型本身就是int类型,在数据转换的之后还是int类型,
# 所以可以不用加int()来强制类型转换,如果是带引号的str类型或者其他类型,那就需要进行类型转换,加了也更保险一点
data_dict["province"],
)
record_list.append(record)

f.close()
return record_list


if __name__ == "__main__":
text_file_reader = TextFileReader("D:/Desktop/Python/2011年1月销售数据.txt")
json_file_reader = JsonFileReader("D:/Desktop/Python/2011年2月销售数据JSON.txt")
list1 = text_file_reader.read_data()
list2 = json_file_reader.read_data()

for i in list1:
print(i)

for i in list2:
print(i)

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""
面向对象,数据分析案例,主业务逻辑代码
实现步骤:
1. 设计一个类,可以完成数据的封装
2. 设计一个抽象类,定义文件读取的相关功能,并使用子类实现具体功能
3. 读取文件,生产数据对象
4. 进行数据需求的逻辑计算(计算每一天的销售额)
5. 通过PyEcharts进行图形绘制
"""

from matplotlib import pyplot as plt
from file_define import TextFileReader, JsonFileReader
from data_define import Record


text_file_reader = TextFileReader("D:/Desktop/Python/2011年1月销售数据.txt")
json_file_reader = JsonFileReader("D:/Desktop/Python/2011年2月销售数据JSON.txt")

jan_data: list[Record] = text_file_reader.read_data()
feb_data: list[Record] = json_file_reader.read_data()
# 将2个月份的数据合并为1个list来存储
# all_data: list[Record] = jan_data + feb_data
jan_data.extend(feb_data) # 用我们之前学过的extend()方法也是没问题的

# 开始进行数据计算
# {"2011-01-01": 1534, "2011-01-02": 300, "2011-01-03": 650}
data_dict = {} # 再次提示,字典里用的是键值对的形式,如上↑
# for record in all_data:
for record in jan_data:
if record.date in data_dict.keys():
# 当前日期已经有记录了,把新记录的钱和老记录的钱做累加就行了
data_dict[record.date] += record.money
else:
data_dict[record.date] = record.money
print(data_dict)

# 可视化图表开发
# 设置x轴和y轴数据
x = list(data_dict.keys()) # x轴数据
y = list(data_dict.values()) # y轴数据

plt.rcParams["font.sans-serif"] = ["SimHei"] # 用于正常显示中文标签
plt.rcParams["axes.unicode_minus"] = False # 用来正常显示负号

# 创建柱状图
plt.figure(figsize=(12, 6)) # 因为x轴数据太多,需要大一点的画布
plt.bar(x, y, color="#32a5d7", label="销售额") # 绘制柱状图

# 添加图表标题和轴标签
plt.title("每日销售额")
plt.xlabel("日期")
plt.ylabel("销售额")
plt.legend()

# 显示图表
plt.xticks(rotation=45) # 旋转x轴标签,防止重叠
plt.show()

  图片截取自:Spyder 6