复习:在前面我们已经学习了Pandas基础,第二章我们开始进入数据分析的业务部分,在第二章第一节的内容中,我们学习了数据的清洗,这一部分十分重要,只有数据变得相对干净,我们之后对数据的分析才可以更有力。而这一节,我们要做的是数据重构,数据重构依旧属于数据理解(准备)的范围。

开始之前,导入numpy、pandas包和数据

1
2
3
# 导入基本库
import numpy as np
import pandas as pd
1
2
3
# 载入data文件中的:train-left-up.csv
text=pd.read_csv('../第二章项目集合/data/train-left-up.csv')
text.head()
PassengerId Survived Pclass Name
0 1 0 3 Braund, Mr. Owen Harris
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th…
2 3 1 3 Heikkinen, Miss. Laina
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel)
4 5 0 3 Allen, Mr. William Henry

2 第二章:数据重构

2.4 数据的合并

2.4.1 任务一:将data文件夹里面的所有数据都载入,观察数据的之间的关系

1
2
3
4
5
6
7
#写入代码
text_left_up=pd.read_csv('../第二章项目集合/data/train-left-up.csv')
text_left_down=pd.read_csv('../第二章项目集合/data/train-left-down.csv')
text_right_up=pd.read_csv('../第二章项目集合/data/train-right-up.csv')
text_right_down=pd.read_csv('../第二章项目集合/data/train-right-down.csv')
text_left_up.head()

PassengerId Survived Pclass Name
0 1 0 3 Braund, Mr. Owen Harris
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th…
2 3 1 3 Heikkinen, Miss. Laina
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel)
4 5 0 3 Allen, Mr. William Henry

【提示】结合之前我们加载的train.csv数据,大致预测一下上面的数据是什么

2.4.2:任务二:使用concat方法:将数据train-left-up.csv和train-right-up.csv横向合并为一张表,并保存这张表为result_up

1
2
3
4
5
#写入代码
#pandas.concat 是 Pandas 中用于连接 Series 或 DataFrame 对象的核心方法,支持横向(列方向)或纵向(行方向)拼接
list_up = [text_left_up,text_right_up]
result_up = pd.concat(list_up,axis=1)
result_up.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

2.4.3 任务三:使用concat方法:将train-left-down和train-right-down横向合并为一张表,并保存这张表为result_down。然后将上边的result_up和result_down纵向合并为result。

1
2
3
4
5
6
#写入代码
list_down=[text_left_down,text_right_down]
result_down = pd.concat(list_down,axis=1)
result = pd.concat([result_up,result_down])
result.head()

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

2.4.4 任务四:使用DataFrame自带的方法join方法和append:完成任务二和任务三的任务

1
2
3
4
5
#写入代码
resul_up = text_left_up.join(text_right_up)
result_down = text_left_down.join(text_right_down)
result = result_up.append(result_down)
result.head()

2.4.5 任务五:使用Panads的merge方法和DataFrame的append方法:完成任务二和任务三的任务

1
2
3
4
5
6
7
8
9
#写入代码
'''
该代码使用 pandas.merge 方法,以索引(index)为键,将两个 DataFrame (text_left_up 和 text_right_up) 横向合并。
'''
result_up = pd.merge(text_left_up,text_right_up,left_index=True,right_index=True)
result_down = pd.merge(text_left_down,text_right_down,left_index=True,right_index=True)
result = resul_up.append(result_down)
result.head()

【思考】对比merge、join以及concat的方法的不同以及相同。思考一下在任务四和任务五的情况下,为什么都要求使用DataFrame的append方法,如何只要求使用merge或者join可不可以完成任务四和任务五呢?

2.4.6 任务六:完成的数据保存为result.csv

1
2
3
4
#写入代码

result.to_csv('result.csv')
result.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

2.5 换一种角度看数据

2.5.1 任务一:将我们的数据变为Series类型的数据

1
2
3
4
5
#写入代码
#text.stack() 是 Pandas 中用于将 DataFrame 的列旋转为行的方法
text = pd.read_csv('result.csv')
unit_result=text.stack().head(20)
unit_result.head()
0  Unnamed: 0                           0
   PassengerId                          1
   Survived                             0
   Pclass                               3
   Name           Braund, Mr. Owen Harris
dtype: object
1
2
3
#写入代码

unit_result.to_csv('unit_result.csv')

复习:数据分析的第一步,加载数据我们已经学习完毕了。当数据展现在我们面前的时候,我们所要做的第一步就是认识他,今天我们要学习的就是了解字段含义以及初步观察数据

1 第一章:数据载入及初步观察

1.4 知道你的数据叫什么

我们学习pandas的基础操作,那么上一节通过pandas加载之后的数据,其数据类型是什么呢?

开始前导入numpy和pandas

1
2
import numpy as np
import pandas as pd

1.4.1 任务一:pandas中有两个数据类型DateFrame和Series,通过查找简单了解他们。然后自己写一个关于这两个数据类型的小例子🌰[开放题]

1
2
3
4
5
#我们举的例子
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
example_1 = pd.Series(sdata)
example_1

Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64
1
2
3
4
5
#我们举的例子
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
example_2 = pd.DataFrame(data)
example_2
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2

1.4.2 任务二:根据上节课的方法载入”train.csv”文件

1
2
#写入代码
df=pd.read_csv('./titanic/train.csv')

也可以加载上一节课保存的”train_chinese.csv”文件。通过翻译版train_chinese.csv熟悉了这个数据集,然后我们对trian.csv来进行操作 #### 1.4.3 任务三:查看DataFrame数据的每列的名称

1
2
#写入代码
df.columns
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')

1.4.4任务四:查看”Cabin”这列的所有值[有多种方法]

1
2
#写入代码
df['Cabin'].head(10)
0     NaN
1     C85
2     NaN
3    C123
4     NaN
5     NaN
6     E46
7     NaN
8     NaN
9     NaN
Name: Cabin, dtype: object
1
2
3
#写入代码
df.Cabin.values[:10]
df.Cabin.head(10)
0     NaN
1     C85
2     NaN
3    C123
4     NaN
5     NaN
6     E46
7     NaN
8     NaN
9     NaN
Name: Cabin, dtype: object

1.4.5 任务五:加载文件”test_1.csv”,然后对比”train.csv”,看看有哪些多出的列,然后将多出的列删除

经过我们的观察发现一个测试集test_1.csv有一列是多余的,我们需要将这个多余的列删去

1
2
3
#写入代码
test_1=pd.read_csv('../第一单元项目集合/test_1.csv')
test_1.head()
Unnamed: 0 PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked a
0 0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 100
1 1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C 100
2 2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 100
3 3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S 100
4 4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S 100
1
2
3
4
#写入代码
#test_1.columns
test_1.drop('Unnamed: 0',axis=1,inplace=True)
test_1.columns
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked', 'a'],
      dtype='object')

【思考】还有其他的删除多余的列的方式吗?

1
2
3
# 思考回答
del test_1['Unnamed: 0']
test_1.columns
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked', 'a'],
      dtype='object')

1.4.6 任务六: 将[‘PassengerId’,‘Name’,‘Age’,‘Ticket’]这几个列元素隐藏,只观察其他几个列元素

1
2
#写入代码
df.drop(['PassengerId', 'Name', 'Age','Ticket'], axis=1).head(10)
Survived Pclass Sex SibSp Parch Fare Cabin Embarked
0 0 3 male 1 0 7.2500 NaN S
1 1 1 female 1 0 71.2833 C85 C
2 1 3 female 0 0 7.9250 NaN S
3 1 1 female 1 0 53.1000 C123 S
4 0 3 male 0 0 8.0500 NaN S
5 0 3 male 0 0 8.4583 NaN Q
6 0 1 male 0 0 51.8625 E46 S
7 0 3 male 3 1 21.0750 NaN S
8 1 3 female 0 2 11.1333 NaN S
9 1 2 female 1 0 30.0708 NaN C

【思考】对比任务五和任务六,是不是使用了不一样的方法(函数),如果使用一样的函数如何完成上面的不同的要求呢?

【思考回答】

如果想要完全的删除你的数据结构,使用inplace=True,因为使用inplace就将原数据覆盖了,所以这里没有用

1.5 筛选的逻辑

表格数据中,最重要的一个功能就是要具有可筛选的能力,选出我所需要的信息,丢弃无用的信息。

下面我们还是用实战来学习pandas这个功能。

1.5.1 任务一: 我们以”Age”为筛选条件,显示年龄在10岁以下的乘客信息。

1
2
3
#写入代码
print((df['Age']<10).head(2))
df[df['Age']<10].head(2)
0    False
1    False
Name: Age, dtype: bool
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.075 NaN S
10 11 1 3 Sandstrom, Miss. Marguerite Rut female 4.0 1 1 PP 9549 16.700 G6 S

1.5.2 任务二: 以”Age”为条件,将年龄在10岁以上和50岁以下的乘客信息显示出来,并将这个数据命名为midage

1
2
3
#写入代码
midage=df[(df['Age']>10) & (df['Age']<50)]
midage

【提示】了解pandas的条件筛选方式以及如何使用交集和并集操作

1.5.3 任务三:将midage的数据中第100行的”Pclass”和”Sex”的数据显示出来

1
2
3
4
#写入代码
midage = midage.reset_index(drop=True)
print(midage)
midage.loc[1][['Pclass', 'Sex']]
     PassengerId  Survived  Pclass  \
0              1         0       3   
1              2         1       1   
2              3         1       3   
3              4         1       1   
4              5         0       3   
..           ...       ...     ...   
571          886         0       3   
572          887         0       2   
573          888         1       1   
574          890         1       1   
575          891         0       3   

                                                  Name     Sex   Age  SibSp  \
0                              Braund, Mr. Owen Harris    male  22.0      1   
1    Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1   
2                               Heikkinen, Miss. Laina  female  26.0      0   
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1   
4                             Allen, Mr. William Henry    male  35.0      0   
..                                                 ...     ...   ...    ...   
571               Rice, Mrs. William (Margaret Norton)  female  39.0      0   
572                              Montvila, Rev. Juozas    male  27.0      0   
573                       Graham, Miss. Margaret Edith  female  19.0      0   
574                              Behr, Mr. Karl Howell    male  26.0      0   
575                                Dooley, Mr. Patrick    male  32.0      0   

     Parch            Ticket     Fare Cabin Embarked  
0        0         A/5 21171   7.2500   NaN        S  
1        0          PC 17599  71.2833   C85        C  
2        0  STON/O2. 3101282   7.9250   NaN        S  
3        0            113803  53.1000  C123        S  
4        0            373450   8.0500   NaN        S  
..     ...               ...      ...   ...      ...  
571      5            382652  29.1250   NaN        Q  
572      0            211536  13.0000   NaN        S  
573      0            112053  30.0000   B42        S  
574      0            111369  30.0000  C148        C  
575      0            370376   7.7500   NaN        Q  

[576 rows x 12 columns]



Pclass         1
Sex       female
Name: 1, dtype: object

【提示】在抽取数据中,我们希望数据的相对顺序保持不变,用什么函数可以达到这个效果呢?

1.5.4 任务四:使用loc方法将midage的数据中第100,105,108行的”Pclass”,“Name”和”Sex”的数据显示出来

1
2
#写入代码
midage.loc[[100,105,108],['Pclass','Name','Sex']]
Pclass Name Sex
100 2 Byles, Rev. Thomas Roussel Davids male
105 3 Cribb, Mr. John Hatfield male
108 3 Calic, Mr. Jovo male

1.5.5 任务五:使用iloc方法将midage的数据中第100,105,108行的”Pclass”,“Name”和”Sex”的数据显示出来

1
2
#写入代码
midage.iloc[[100,105,108],[2,3,4]]
Pclass Name Sex
149 2 Byles, Rev. Thomas Roussel Davids male
160 3 Cribb, Mr. John Hatfield male
163 3 Calic, Mr. Jovo male

【思考】对比ilocloc的异同

1
2
3
4
5
6
7
8
9
# *   当你需要根据**标签名称**(如行索引名或列名)来选取数据时,使用 `loc`。这使得代码更具可读性,因为你可以直接看到你正在操作的标签。
# * 当你需要根据**整数位置**来选取数据时(不关心标签名称,或者标签不是整数),使用 `iloc`。这在处理没有有意义标签的 DataFrame,或者需要进行与位置相关的操作时很有用。
# * **注意**:如果 DataFrame 的索引是默认的整数索引 (0, 1, 2, ...),那么 `loc` 和 `iloc` 在使用单个整数或整数切片进行行选择时,行为可能会相似,但这可能会导致混淆。
# * 例如,如果 `df.index` 是 `[0, 1, 2, 5, 6]`:
# * `df.loc[0]` 会选择索引标签为 `0` 的行。
# * `df.iloc[0]` 也会选择第一行(即索引标签为 `0` 的行)。
# * `df.loc[3]` 会报错,因为没有索引标签为 `3`。
# * `df.iloc[3]` 会选择第四行(即索引标签为 `5` 的行)。
# * 为了避免混淆,最佳实践是:当你知道你正在使用标签时,明确使用 `loc`;当你知道你正在使用整数位置时,明确使用 `iloc`。

复习:回顾学习完第一章,我们对泰坦尼克号数据有了基本的了解,也学到了一些基本的统计方法,第二章中我们学习了数据的清理和重构,使得数据更加的易于理解;今天我们要学习的是第二章第三节:数据可视化,主要给大家介绍一下Python数据可视化库Matplotlib,在本章学习中,你也许会觉得数据很有趣。在打比赛的过程中,数据可视化可以让我们更好的看到每一个关键步骤的结果如何,可以用来优化方案,是一个很有用的技巧。

2 第二章:数据可视化

开始之前,导入numpy、pandas以及matplotlib包和数据

1
2
3
4
5
6
# 加载所需的库
# 如果出现 ModuleNotFoundError: No module named 'xxxx'
# 你只需要在终端/cmd下 pip install xxxx 即可
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
1
2
3
#加载result.csv这个数据
text = pd.read_csv(r'result.csv')
text.head()
Unnamed: 0 PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

2.7 如何让人一眼看懂你的数据?

《Python for Data Analysis》第九章

2.7.1 任务一:跟着书本第九章,了解matplotlib,自己创建一个数据项,对其进行基本可视化

【思考】最基本的可视化图案有哪些?分别适用于那些场景?(比如折线图适合可视化某个属性值随时间变化的走势)

1
2
3
#思考回答
#这一部分需要了解可视化图案的的逻辑,知道什么样的图案可以表达什么样的信号b

2.7.2 任务二:可视化展示泰坦尼克号数据集中男女中生存人数分布情况(用柱状图试试)。

1
2
3
4
5
6
#代码编写
sex = text.groupby('Sex')['Survived'].sum()
sex.plot.bar()
plt.title('survived_count')
plt.show()

第二章:第四节数据可视化-课程_10_0

【思考】计算出泰坦尼克号数据集中男女中死亡人数,并可视化展示?如何和男女生存人数可视化柱状图结合到一起?看到你的数据可视化,说说你的第一感受(比如:你一眼看出男生存活人数更多,那么性别可能会影响存活率)。

1
2
3
#思考题回答


2.7.3 任务三:可视化展示泰坦尼克号数据集中男女中生存人与死亡人数的比例图(用柱状图试试)。

1
2
3
4
5
6
7
#代码编写
# 提示:计算男女中死亡人数 1表示生存,0表示死亡
text.groupby(['Sex','Survived'])['Survived'].count().unstack().plot(kind='bar',stacked='True')
plt.title('survived_count')
plt.ylabel('count')


Text(0, 0.5, 'count')
第二章:第四节数据可视化-课程_14_1

【提示】男女这两个数据轴,存活和死亡人数按比例用柱状图表示

2.7.4 任务四:可视化展示泰坦尼克号数据集中不同票价的人生存和死亡人数分布情况。(用折线图试试)(横轴是不同票价,纵轴是存活人数)

【提示】对于这种统计性质的且用折线表示的数据,你可以考虑将数据排序或者不排序来分别表示。看看你能发现什么?

1
2
3
4
5
6
7
#代码编写
# 计算不同票价中生存与死亡人数 1表示生存,0表示死亡
#print(text.groupby(['Fare'])['Survived'].value_counts())
fare_sur = text.groupby(['Fare'])['Survived'].value_counts().sort_values(ascending=False)
fare_sur


Fare     Survived
8.0500   0           38
7.8958   0           37
13.0000  0           26
7.7500   0           22
26.0000  0           16
                     ..
6.9500   0            1
6.9750   0            1
         1            1
7.0458   0            1
7.1417   1            1
Name: count, Length: 330, dtype: int64
1
2
3
4
5
# 排序后绘折线图
fig = plt.figure(figsize=(20, 18))
fare_sur.plot(grid=True)
plt.legend()
plt.show()
第二章:第四节数据可视化-课程_19_0
1
2
3
4
5
6
7
8
# 写入代码
# 排序前绘折线图
fare_sur1 = text.groupby(['Fare'])['Survived'].value_counts()
fare_sur1
fig = plt.figure(figsize=(20, 18))
fare_sur1.plot(grid=True)
plt.legend()
plt.show()
第二章:第四节数据可视化-课程_20_0

2.7.5 任务五:可视化展示泰坦尼克号数据集中不同仓位等级的人生存和死亡人员的分布情况。(用柱状图试试)

1
2
3
4
#代码编写
# 1表示生存,0表示死亡
pclass_sur = text.groupby(['Pclass'])['Survived'].value_counts()
pclass_sur
Pclass  Survived
1       1           136
        0            80
2       0            97
        1            87
3       0           372
        1           119
Name: count, dtype: int64
1
2
import seaborn as sns
sns.countplot(x="Pclass", hue="Survived", data=text)
<Axes: xlabel='Pclass', ylabel='count'>
第二章:第四节数据可视化-课程_23_1

【思考】看到这个前面几个数据可视化,说说你的第一感受和你的总结

1
2
3
#思考题回答


2.7.6 任务六:可视化展示泰坦尼克号数据集中不同年龄的人生存与死亡人数分布情况。(不限表达方式)

1
2
3
4
5
#代码编写
facet = sns.FacetGrid(text, hue="Survived",aspect=3)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, text['Age'].max()))
facet.add_legend()
/root/.pyenv/versions/3.11.1/lib/python3.11/site-packages/seaborn/axisgrid.py:854: FutureWarning: 

`shade` is now deprecated in favor of `fill`; setting `fill=True`.
This will become an error in seaborn v0.14.0; please update your code.

  func(*plot_args, **plot_kwargs)
/root/.pyenv/versions/3.11.1/lib/python3.11/site-packages/seaborn/axisgrid.py:854: FutureWarning: 

`shade` is now deprecated in favor of `fill`; setting `fill=True`.
This will become an error in seaborn v0.14.0; please update your code.

  func(*plot_args, **plot_kwargs)

<seaborn.axisgrid.FacetGrid at 0x7f9c6bce1f50>
第二章:第四节数据可视化-课程_27_2

2.7.7 任务七:可视化展示泰坦尼克号数据集中不同仓位等级的人年龄分布情况。(用折线图试试)

1
2
3
4
5
6
7
#代码编写

text.Age[text.Pclass == 1].plot(kind='kde')
text.Age[text.Pclass == 2].plot(kind='kde')
text.Age[text.Pclass == 3].plot(kind='kde')
plt.xlabel("age")
plt.legend((1,2,3),loc="best")
<matplotlib.legend.Legend at 0x7f9c69946e90>
第二章:第四节数据可视化-课程_29_1

【思考】上面所有可视化的例子做一个总体的分析,你看看你能不能有自己发现

【总结】到这里,我们的可视化就告一段落啦,如果你对数据可视化极其感兴趣,你还可以了解一下其他可视化模块,如:pyecharts,bokeh等。

如果你在工作中使用数据可视化,你必须知道数据可视化最大的作用不是炫酷,而是最快最直观的理解数据要表达什么,你觉得呢?

第三章 模型搭建和评估-评估

根据之前的模型的建模,我们知道如何运用sklearn这个库来完成建模,以及我们知道了的数据集的划分等等操作。那么一个模型我们怎么知道它好不好用呢?以至于我们能不能放心的使用模型给我的结果呢?那么今天的学习的评估,就会很有帮助。

加载下面的库

1
2
3
4
5
6
7
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from IPython.display import Image
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
1
%matplotlib inline
1
2
3
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.figsize'] = (10, 6) # 设置输出图片大小

任务:加载数据并分割测试集和训练集

1
2
3
#写入代码

from sklearn.model_selection import train_test_split
1
2
3
4
5
6
7
#写入代码
# 一般先取出X和y后再切割,有些情况会使用到未切割的,这时候X和y就可以用,x是清洗好的数据,y是我们要预测的存活数据'Survived'
data = pd.read_csv('clear_data.csv')
train = pd.read_csv('train.csv')
X = data
y = train['Survived']

1
2
3
4
#写入代码
# 对数据集进行切割
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)

1
2
3
4
5
#写入代码
# 默认参数逻辑回归模型
lr = LogisticRegression()
lr.fit(X_train, y_train)

/root/.pyenv/versions/3.11.1/lib/python3.11/site-packages/sklearn/linear_model/_logistic.py:465: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(

模型评估

  • 模型评估是为了知道模型的泛化能力。
  • 交叉验证(cross-validation)是一种评估泛化性能的统计学方法,它比单次划分训练集和测试集的方法更加稳定、全面。
  • 在交叉验证中,数据被多次划分,并且需要训练多个模型。
  • 最常用的交叉验证是 k 折交叉验证(k-fold cross-validation),其中 k 是由用户指定的数字,通常取 5 或 10。
  • 准确率(precision)度量的是被预测为正例的样本中有多少是真正的正例
  • 召回率(recall)度量的是正类样本中有多少被预测为正类
  • f-分数是准确率与召回率的调和平均

【思考】:将上面的概念进一步的理解,大家可以做一下总结

1
2
3
#思考回答:


任务一:交叉验证

  • 用10折交叉验证来评估之前的逻辑回归模型
  • 计算交叉验证精度的平均值
1
2
#提示:交叉验证
Image('Snipaste_2020-01-05_16-37-56.png')
第三章模型建立和评估—评价-课程_16_0

提示4

  • 交叉验证在sklearn中的模块为sklearn.model_selection
1
2
3
#写入代码
from sklearn.model_selection import cross_val_score

1
2
3
#写入代码
lr = LogisticRegression(C=100)
scores = cross_val_score(lr, X_train, y_train, cv=10)
1
2
3
4
#写入代码
# 平均交叉验证分数
print("Average cross-validation score: {:.2f}".format(scores.mean()))

Average cross-validation score: 0.78

思考4

  • k折越多的情况下会带来什么样的影响?
1
2
3
4
5
6
#思考回答
# 当 k 越大时:
# 1. 每次训练使用的数据更多,评估偏差(bias)降低
# 2. 每次测试集样本更少,评估方差(variance)增大
# 3. 需要训练 k 个模型,计算开销显著增加

任务二:混淆矩阵

  • 计算二分类问题的混淆矩阵
  • 计算精确率、召回率以及f-分数

【思考】什么是二分类问题的混淆矩阵,理解这个概念,知道它主要是运算到什么任务中的

1
2
3
4
5
6
7
8
9
#思考回答
# 混淆矩阵(confusion matrix)是一个 2×2 的表格,用于二分类任务中展示模型预测结果与真实标签的对应关系:
# - True Positive (TP):真实为正类,预测也为正类
# - False Positive (FP):真实为负类,却被误预测为正类
# - False Negative (FN):真实为正类,却被误预测为负类
# - True Negative (TN):真实为负类,预测也为负类
# 通过混淆矩阵,可以进一步计算精确率(Precision)、召回率(Recall)、F1 分数等指标,
# 帮助我们评估模型在不同类型错误上的表现,常用于分类模型的性能评估和错误分析。

1
2
#提示:混淆矩阵
Image('Snipaste_2020-01-05_16-38-26.png')
第三章模型建立和评估—评价-课程_27_0
1
2
3
#提示:准确率 (Accuracy),精确度(Precision),Recall,f-分数计算方法
Image('Snipaste_2020-01-05_16-39-27.png')

第三章模型建立和评估—评价-课程_28_0

提示5

  • 混淆矩阵的方法在sklearn中的sklearn.metrics模块
  • 混淆矩阵需要输入真实标签和预测标签
  • 精确率、召回率以及f-分数可使用classification_report模块
1
2
3
#写入代码
from sklearn.metrics import confusion_matrix

1
2
3
4
5
#写入代码
# 训练模型
lr = LogisticRegression(C=100)
lr.fit(X_train, y_train)

/root/.pyenv/versions/3.11.1/lib/python3.11/site-packages/sklearn/linear_model/_logistic.py:465: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
1
2
3
#写入代码
# 模型预测结果
pred = lr.predict(X_train)
1
2
3
4
#写入代码
# 混淆矩阵
confusion_matrix(y_train, pred)

array([[355,  57],
       [ 82, 174]])
1
2
3
from sklearn.metrics import classification_report
# 精确率、召回率以及f1-score
print(classification_report(y_train, pred))
              precision    recall  f1-score   support

           0       0.81      0.86      0.84       412
           1       0.75      0.68      0.71       256

    accuracy                           0.79       668
   macro avg       0.78      0.77      0.78       668
weighted avg       0.79      0.79      0.79       668

【思考】 * 如果自己实现混淆矩阵的时候该注意什么问题

1
2
3
4
5
6
7
8
9
#思考回答
# 如果自己实现混淆矩阵,需要注意:
# 1. 明确行列含义:通常行是真实标签,列是预测标签,并保持一致。
# 2. 类别顺序要固定:最好指定 labels 列表,避免类别稀疏时错位。
# 3. 初始化大小为 n_classes×n_classes 的零矩阵。
# 4. 索引时使用整数或统一的类别映射,避免类型不一致。
# 5. 对每个样本累加到对应的 [真实, 预测] 单元格,最后矩阵元素之和应等于样本数。
# 6. 对于未出现的类别,矩阵对应行或列应保留 0。

任务三:ROC曲线

  • 绘制ROC曲线

【思考】什么是ROC曲线,OCR曲线的存在是为了解决什么问题?

1
2
3
4
5
6
7
#思考
# ROC曲线(Receiver Operating Characteristic Curve)是一条以假正例率(FPR)为横坐标、
# 真正例率(TPR)为纵坐标绘制的曲线,展示模型在不同阈值下的分类性能变化。
# 它解决了单一阈值下评估不全面的问题,通过曲线下的面积(AUC)能够衡量模型整体区分正负样本的能力;
# 对类别不平衡更稳健,可在不同模型或参数设置间进行客观比较。


提示6

  • ROC曲线在sklearn中的模块为sklearn.metrics
  • ROC曲线下面所包围的面积越大越好
1
2
3
#写入代码
from sklearn.metrics import roc_curve

1
print(lr.decision_function(X_test))
[-1.7776276  -1.68901519 -2.9343385  -2.73339993 -0.7425476   0.1771919
  0.42300886 -0.95177507 -2.19297241 -2.09492243 -2.09876666 -2.24379328
 -0.72898893 -0.74448703  1.55206252  2.26736362 -3.0615053  -1.45551632
  1.82143942  1.10174703  2.80348253  2.20862227 -2.08595792 -1.98565326
 -2.62459231  2.61608127  2.52054836  0.46386814 -2.26805651 -1.89799476
 -4.40221097 -2.45118004 -2.11507984  0.25727282  1.56507901 -3.49922092
  0.09517543  3.1727335  -0.66659502 -2.16889122 -2.31738004 -0.75154631
  1.34173247 -0.68691348 -2.38317701 -1.48352807  3.30441868  0.37836543
  0.15120699 -2.39554116  0.71230509 -2.94049784  0.0526656  -0.12124772
  0.21937853 -0.95736671 -2.91315052  1.73227025 -2.30451919 -0.11949728
 -2.40406452 -1.23217853 -3.04709277 -2.51149884 -2.91275507  0.36741872
  1.88515182 -1.73344723  1.61180838 -2.64456699 -2.82671595 -1.32885535
 -1.89201447 -2.38194062  1.14830497  0.7324757   3.41575634 -0.04718518
  1.99047031  0.71098531 -2.5002286   2.11220527  1.35687779 -4.65208202
 -0.50164169 -2.21847127 -0.27744568 -2.1098023  -2.28203956 -2.24087733
  1.49913758 -0.46745632 -1.76590269 -3.13694507 -2.48969764 -2.52447108
 -0.31359417 -2.62456277  0.10812447 -3.22505518 -0.54301462 -1.31398633
 -2.45637232 -0.9392769  -1.99910791 -0.01952273  0.16386412  1.17043699
  0.83571934 -0.30892412 -2.56236834 -2.52630696 -2.15878988  3.38005162
 -1.63316112 -2.0470374   1.16802525  1.96428556 -0.85542758 -0.84711271
 -2.3923425  -2.27467461  1.27340371 -0.16738478  2.77379952 -0.91636487
  3.49337899  2.22265823 -1.03898765 -1.79576035  3.05405598 -1.72625544
 -2.08233698  0.14427761 -2.03826492 -1.87510703 -2.43040363  0.88364821
 -2.31722422  1.21479438 -2.19509856 -1.96948465  2.90456606  1.22909197
 -0.60993113 -2.40508898  1.79832298 -2.33619419 -1.76964851  0.54894164
  0.56920781 -1.65544357 -2.18783672 -2.51890544 -1.1167812   1.85506633
 -2.14366192  2.56003678  1.79741811  2.22038003 -0.93948297  2.11029939
  3.66773152  3.37255532 -1.62079149 -0.21922341 -2.93532548 -1.8851028
 -0.11223495 -0.89402373 -2.79168773  0.58319665 -1.20213471  2.11583429
 -1.78550619 -1.21648746 -2.91538781 -2.80005448 -2.74359191 -0.06775047
 -1.28645408 -1.17048578 -0.1176852  -1.59958242 -0.65901928 -2.40701243
  0.57575073 -3.0756839   1.53932753 -2.49031769 -3.03266822  0.30539932
 -0.05523861 -0.24431132 -2.36483723  3.25595248 -2.11664845 -1.97728592
 -2.04509461 -3.07727841 -1.11942703 -3.38920295 -2.59088459 -3.55978164
  0.22449105 -0.3214215   0.05735696  0.02061023 -3.01544378 -0.77973619
 -1.39798016 -3.10075724 -4.80621573 -3.01948006  3.44366918 -2.88193813
 -2.01992513 -0.09559774  0.91447527 -1.13270082 -2.45426968 -1.91415803
 -0.08403516]
1
2
3
4
5
6
7
8
9
10
11
#写入代码
# 计算ROC曲线的假正例率(FPR)、真正例率(TPR)和阈值
fpr, tpr, thresholds = roc_curve(y_test, lr.decision_function(X_test))
plt.plot(fpr, tpr, label="ROC Curve")
plt.xlabel("FPR")
plt.ylabel("TPR (recall)")
# 找到最接近于0的阈值
close_zero = np.argmin(np.abs(thresholds))
plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="threshold zero", fillstyle="none", c='k', mew=2)
plt.legend(loc=4)

第三章模型建立和评估—评价-课程_44_3

思考6

  • 对于多分类问题如何绘制ROC曲线
1
2
3
4
#思考回答



【思考】你能从这条OCR曲线的到什么信息?这些信息可以做什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#思考回答
# 从ROC曲线中可以得到以下信息:
# 1. 模型整体性能:曲线下的面积 (AUC - Area Under the Curve) 是一个常用的评估指标。
# - AUC = 1:完美分类器。
# - AUC = 0.5:随机分类器(无区分能力,ROC曲线接近对角线)。
# - AUC > 0.5:模型优于随机猜测。AUC越大,模型区分正负样本的能力越强。
# - AUC < 0.5:模型表现差于随机猜测(可能标签反了或者模型非常差)。
# 2. 不同阈值下的权衡:ROC曲线展示了在所有可能的分类阈值下,真正例率 (TPR) 与假正例率 (FPR) 之间的关系。
# - 曲线上的每个点代表一个特定的阈值。
# - 曲线越靠近左上角 (FPR低, TPR高),说明模型在较低的假正例率下能达到较高的真正例率,性能越好。
# 3. 模型的区分能力:曲线的形状可以反映模型区分正负样本的能力。如果曲线显著高于对角线,说明模型具有较好的区分能力。
# 4. 阈值选择的依据:可以根据业务需求,在ROC曲线上选择一个合适的平衡点(即选择一个阈值)。
# - 例如,如果更关注减少漏报(提高TPR),可以选择曲线上TPR较高的点,即使FPR可能略高。
# - 如果更关注减少误报(降低FPR),可以选择曲线上FPR较低的点,即使TPR可能略低。
# - 图中标记的 "threshold zero" 点通常是模型默认的分类阈值对应的性能点。


【回顾&引言】前面一章的内容大家可以感觉到我们主要是对基础知识做一个梳理,让大家了解数据分析的一些操作,主要做了数据的各个角度的观察。那么在这里,我们主要是做数据分析的流程性学习,主要是包括了数据清洗以及数据的特征处理,数据重构以及数据可视化。这些内容是为数据分析最后的建模和模型评价做一个铺垫。

开始之前,导入numpy、pandas包和数据

1
2
3
#加载所需的库
import numpy as np
import pandas as pd
1
2
3
#加载数据train.csv
df = pd.read_csv('./titanic/train.csv')
df.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S

2 第二章:数据清洗及特征处理

我们拿到的数据通常是不干净的,所谓的不干净,就是数据中有缺失值,有一些异常点等,需要经过一定的处理才能继续做后面的分析或建模,所以拿到数据的第一步是进行数据清洗,本章我们将学习缺失值、重复值、字符串和数据转换等操作,将数据清洗成可以分析或建模的亚子。

2.1 缺失值观察与处理

我们拿到的数据经常会有很多缺失值,比如我们可以看到Cabin列存在NaN,那其他列还有没有缺失值,这些缺失值要怎么处理呢

2.1.1 任务一:缺失值观察

  1. 请查看每个特征缺失值个数
  2. 请查看Age, Cabin, Embarked列的数据 以上方式都有多种方式,所以大家多多益善
1
2
#写入代码
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
1
2
#写入代码
df.isnull().sum()
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
1
2
3
#写入代码
df[['Age','Cabin','Embarked']].head(3)
#df['Age'].head(3)
Age Cabin Embarked
0 22.0 NaN S
1 38.0 C85 C
2 26.0 NaN S

外层 [] 是 DataFrame 的索引操作符。 内层 [] 是 Python 原生的列表语法,用于传递多个列名。

2.1.2 任务二:对缺失值进行处理

(1)处理缺失值一般有几种思路

  1. 请尝试对Age列的数据的缺失值进行处理

  2. 请尝试使用不同的方法直接对整张表的缺失值进行处理

1
2
3
4
5
#处理缺失值的一般思路:
#提醒:可使用的函数有--->dropna函数与fillna函数
#print(df.head(3))
df[df['Age']==None]=0
df.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
1
2
3
#写入代码
df[df['Age'].isnull()]
df[df['Age'].isnull()] = 0
1
2
#写入代码
df[df['Age'] == np.nan] = 0

【思考1】dropna和fillna有哪些参数,分别如何使用呢?

1
2
#dropna() 是 Pandas 中用于删除包含缺失值(NaN 或 None)的行或列的函数。其核心作用是清理数据中的缺失值,适用于数据清洗阶段。
df.dropna().head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
5 0 0 0 0 0 0.0 0 0 0 0.0000 0 0
1
2
#fillna() 是 Pandas 中用于填充缺失值(NaN 或 None)的核心函数,常用于数据清洗阶段。其核心作用是将缺失值替换为合理值,以便后续分析或建模。
df.fillna(0).head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 0 S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 0 S

【思考】检索空缺值用np.nan,None以及.isnull()哪个更好,这是为什么?如果其中某个方式无法找到缺失值,原因又是为什么?

【参考】https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dropna.html

【参考】https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html

2.2 重复值观察与处理

由于这样那样的原因,数据中会不会存在重复值呢,如果存在要怎样处理呢

2.2.1 任务一:请查看数据中的重复值

1
2
3
#写入代码
#df.duplicated()返回一个布尔序列 (Series),标记每一行是否为重复行
df[df.duplicated()]
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
17 0 0 0 0 0 0.0 0 0 0 0.0 0 0
19 0 0 0 0 0 0.0 0 0 0 0.0 0 0
26 0 0 0 0 0 0.0 0 0 0 0.0 0 0
28 0 0 0 0 0 0.0 0 0 0 0.0 0 0
29 0 0 0 0 0 0.0 0 0 0 0.0 0 0
859 0 0 0 0 0 0.0 0 0 0 0.0 0 0
863 0 0 0 0 0 0.0 0 0 0 0.0 0 0
868 0 0 0 0 0 0.0 0 0 0 0.0 0 0
878 0 0 0 0 0 0.0 0 0 0 0.0 0 0
888 0 0 0 0 0 0.0 0 0 0 0.0 0 0

176 rows × 12 columns

2.2.2 任务二:对重复值进行处理

(1)重复值有哪些处理方式呢?

(2)处理我们数据的重复值

方法多多益善

1
2
3
4
#重复值有哪些处理方式:
#删除 DataFrame 中的重复行(完全相同的行只保留一次)。
df = df.drop_duplicates()
df.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

2.2.3 任务三:将前面清洗的数据保存为csv格式

1
2
3
4
#写入代码

df.to_csv('test_clear.csv')

2.3 特征观察与处理

我们对特征进行一下观察,可以把特征大概分为两大类:
数值型特征:Survived ,Pclass, Age ,SibSp, Parch, Fare,其中Survived, Pclass为离散型数值特征,Age,SibSp, Parch, Fare为连续型数值特征
文本型特征:Name, Sex, Cabin,Embarked, Ticket,其中Sex, Cabin, Embarked, Ticket为类别型文本特征,数值型特征一般可以直接用于模型的训练,但有时候为了模型的稳定性及鲁棒性会对连续变量进行离散化。文本型特征往往需要转换成数值型特征才能用于建模分析。

2.3.1 任务一:对年龄进行分箱(离散化)处理

  1. 分箱操作是什么?

  2. 将连续变量Age平均分箱成5个年龄段,并分别用类别变量12345表示

  3. 将连续变量Age划分为[0,5) [5,15) [15,30) [30,50) [50,80)五个年龄段,并分别用类别变量12345表示

  4. 将连续变量Age按10% 30% 50% 70% 90%五个年龄段,并用分类变量12345表示

  5. 将上面的获得的数据分别进行保存,保存为csv格式

1
2
3
4
#分箱操作是什么:
'''
分箱操作(Binning)是数据预处理中的一种常用技术,主要用于将连续型数值转换为离散的区间(即“箱子”或“分组”)
'''
1
2
3
4
5
6
#写入代码
#将连续变量Age平均分箱成5个年龄段,并分别用类别变量12345表示
df['AgeBand'] = pd.cut(df['Age'], 5,labels = [1,2,3,4,5])
df.head()


PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked AgeBand
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C 3
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 2
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S 3
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S 3
1
2
3
4
5
#写入代码
#将连续变量Age划分为(0,5] (5,15] (15,30] (30,50] (50,80]五个年龄段,并分别用类别变量12345表示
df['AgeBand'] = pd.cut(df['Age'],[0,5,15,30,50,80],labels = [1,2,3,4,5])
df.head(3)

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked AgeBand
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 3
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C 4
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 3
1
2
3
4
#写入代码
#将连续变量Age按10% 30% 50 70% 90%五个年龄段,并用分类变量12345表示
df['AgeBand'] = pd.qcut(df['Age'],[0,0.1,0.3,0.5,0.7,0.9],labels = [1,2,3,4,5])
df.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked AgeBand
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C 5
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 3
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S 4
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S 4

【参考】https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.cut.html

【参考】https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.qcut.html

2.3.2 任务二:对文本变量进行转换

  1. 查看文本变量名及种类
  2. 将文本变量Sex, Cabin ,Embarked用数值变量12345表示
  3. 将文本变量Sex, Cabin, Embarked用one-hot编码表示
1
2
3
4
5
6
#写入代码
#方法一: value_counts
print(
df['Sex'].value_counts(),
df['Cabin'].value_counts(),
df['Embarked'].value_counts())
Sex
male      453
female    261
0           1
Name: count, dtype: int64 Cabin
B96 B98        4
G6             4
C23 C25 C27    4
F2             3
C22 C26        3
              ..
E36            1
D7             1
C118           1
C99            1
D37            1
Name: count, Length: 135, dtype: int64 Embarked
S    554
C    130
Q     28
0      1
Name: count, dtype: int64
1
2
3
#写入代码
df['Sex'].unique()
df['Sex'].nunique()
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#写入代码
#将类别文本转换为12345

#方法一: replace
df['Sex_num'] = df['Sex'].replace(['male','female'],[1,2])
df.head()
#方法二: map
df['Sex_num'] = df['Sex'].map({'male': 1, 'female': 2})
df.head()
#方法三: 使用sklearn.preprocessing的LabelEncoder
from sklearn.preprocessing import LabelEncoder
for feat in ['Cabin', 'Ticket']:
lbl = LabelEncoder()
label_dict = dict(zip(df[feat].unique(), range(df[feat].nunique())))
#print(label_dict)
df[feat + "_labelEncode"] = df[feat].map(label_dict)
df[feat + "_labelEncode"] = lbl.fit_transform(df[feat].astype(str))

df.head()

/tmp/ipykernel_1400/2627332835.py:5: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
  df['Sex_num'] = df['Sex'].replace(['male','female'],[1,2])
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Age_66.0 Age_70.0 Age_70.5 Age_71.0 Age_74.0 Age_80.0 Embarked_0 Embarked_C Embarked_Q Embarked_S
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 False False False False False False False False False True
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 False False False False False False False True False False
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 False False False False False False False False False True
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 False False False False False False False False False True
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 False False False False False False False False False True

5 rows × 109 columns

1
2
3
4
5
6
7
8
9
10
11
12
#将类别文本转换为one-hot编码

#方法一: OneHotEncoder
for feat in ["Age", "Embarked"]:
x = pd.get_dummies(df["Age"] // 6)
# x = pd.get_dummies(pd.cut(df['Age'],5))
x = pd.get_dummies(df[feat], prefix=feat)
df = pd.concat([df, x], axis=1)
#df[feat] = pd.get_dummies(df[feat], prefix=feat)

df.head()
df.to_csv('temp.csv')

2.3.3 任务三:从纯文本Name特征里提取出Titles的特征(所谓的Titles就是Mr,Miss,Mrs等)

1
2
3
4
#写入代码



1
2
#保存最终你完成的已经清理好的数据

前言

代码仓库zxj-2023/learn_fastapi

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。

关键特性:

  • 快速:可与 NodeJSGo 并肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一
  • 高效编码:提高功能开发速度约 200% 至 300%。*
  • 更少 bug:减少约 40% 的人为(开发者)导致错误。*
  • 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
  • 简单:设计的易于使用和学习,阅读文档的时间更短。
  • 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
  • 健壮:生产可用级别的代码。还有自动生成的交互式文档。
  • 标准化:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema

两个核心组件:Starlette 和 Pydantic

Starlette 负责web部分

Starlette 是 FastAPI 的底层 ASGI(异步服务器网关接口)框架,为 FastAPI 提供了异步编程能力和高性能的网络通信支持。

ASGI(Asynchronous Server Gateway Interface )是一种用于连接 Python Web 服务器和应用程序框架的异步接口标准 ,旨在支持现代 Web 协议(如 WebSocket、HTTP/2)和异步编程模型

Pydantic负责

Pydantic 负责 FastAPI 的数据验证、序列化和自动文档生成

http协议

一、简介

HTTP协议 是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于万维网(WWW: World Wide Web)服务器与本地浏览器之间传输超文本的传送协议。HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

1

二、http协议特性

(1)基于 TCP/IP 协议

http 协议是基于 TCP/IP 协议之上的应用层协议。

(2)基于请求 - 响应模式

HTTP 协议规定,请求从客户端发出,最后服务器端响应应该请求并返回。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有接收到请求之前不会发送响应

(3)无状态保存

HTTP 是一种不保存状态,即无状态(stateless)协议。HTTP 协议自身不对请求和响应之间的通信状态进行保存。也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理。

使用 HTTP 协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的。

(4)短连接

HTTP 1.0 默认使用的是短连接。浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。

HTTP 1.1 起,默认使用长连接。要使用长连接,客户端和服务器的 HTTP 首部的 Connection 都要设置为 keep - alive,才能支持长连接。

HTTP 长连接,指的是复用 TCP 连接。多个 HTTP 请求可以复用同一个 TCP 连接,这就节省了 TCP 连接建立和断开的消耗。

三、http请求协议与响应协议

2

Socket(套接字)是计算机网络中用于实现进程间双向通信的端点抽象,它为应用层进程通过网络协议交换数据提供了统一的接口。具体来说,Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,本质上是一组封装了复杂网络协议的接口,简化了开发者对底层通信细节的操作。

从功能上看,Socket 可以看作是网络通信的“电话插座”:两个设备(如客户端与服务器)通过 Socket 建立连接后,即可像电话通话一样进行数据交换,而端口号则类似于插座上的插孔,用于标识具体的通信进程,且不能被其他进程占用。此外,Socket 包含网络通信必需的五种核心信息,例如使用的协议(TCP/UDP)、本地与远程地址、端口等,构成了网络通信的基本操作单元。

总结而言,Socket 既是通信端点的逻辑概念,也是实现网络应用层交互的关键工具,其设计目标是屏蔽底层协议的复杂性,提供统一的编程接口。

3

GET :请求参数通过 URL 的查询字符串(Query String)传递,数据暴露在地址栏中,例如:https://example.com ?name=value

POST :请求参数存储在请求体(Body)中传输,相对更安全,且支持传输非字符串数据(如文件、二进制等)

一个完整的URL包括:协议、ip、端口、路径、参数

例如:https://www.baidu.com/s?wd=yuan 其中https是协议,www.baidu.com 是IP,端口默认80,/s是路径,参数是wd=yuan

请求方式:get与post请求

  • GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456。POST方法是把提交的数据放在HTTP包的请求体中。
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制

响应状态码:状态码的职责是当客户端向服务器端发送请求时,返回的请求结果。借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了问题。状态码如200 OK,以3位数字和原因组成。

测试http协议格式:请求与响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#web应用程序:遵循http协议
import socket
sock=socket.socket()
sock.bind(("127.0.0.1",8080))
sock.listen(5)
while True:
'''
conn 表示新建立的套接字对象,用于在服务器和客户端之间进行数据传输。
addr 是一个元组,它包含了连接进来的客户端的 IP 地址和端口号。
'''
conn, addr = sock.accept()#阻塞等待客户端连接
data=conn.recv(1024)#请求报文
# data 是一个字节串,包含了客户端发送的请求信息。
print("客户端发送的请求信息:\n",data)
conn.send(b"HTTP/1.1 200 ok\r\nserver:zxj\r\n\r\nhello world")#响应首行+响应头+响应体
conn.close()

测试post请求:urlencoded格式

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
import socket

# 创建 socket 连接
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

# 构造 POST 请求报文
path = "/" # 目标路径
headers = {
"Host": "127.0.0.1",
"Content-Type": "application/x-www-form-urlencoded", # 数据格式
"Content-Length": len("username=admin&password=123456") # 数据长度
}
body = "username=admin&password=123456" # 请求体(表单数据)

# 拼接请求报文
request = f"POST {path} HTTP/1.1\r\n"
for k, v in headers.items():
request += f"{k}: {v}\r\n"
request += "\r\n" # 空行分隔头部与主体
request += body

# 发送请求
client.send(request.encode('utf-8'))

# 接收响应
response = client.recv(4096)
print(response.decode('utf-8'))
client.close()

测试post请求:json格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import json

# 定义请求地址
url = "http://127.0.0.1:8080"
# 定义 JSON 数据(字典格式)
data = {
"username": "admin",
"password": "123456"
}

# 发送 POST 请求
response = requests.post(
url,
json=data # 使用 json 参数自动序列化字典并设置 Content-Type: application/json
)

# 输出响应结果
print("状态码:", response.status_code)
print("响应内容:", response.text) # 使用 text 获取原始响应文本
'''
客户端发送的请求信息:
b'POST / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: python-requests/2.32.2\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 43\r\nContent-Type: application/json\r\n\r\n{"username": "admin", "password": "123456"}'
'''

通过 json=data 参数,requests 会自动将字典转换为 JSON 字符串,并设置请求头 Content-Type: application/json,无需手动调用 json.dumps() 或配置 headers

SSL 验证是指通过 SSL 证书验证网站身份并确保通信安全的过程。其核心目标是确认服务器的真实性、防止身份伪造,并建立加密连接以保护数据传输的安全性

HTTPS(HyperText Transfer Protocol Secure)是以安全为目标的 HTTP 通道,通过在 HTTP 基础上加入加密和身份认证机制,确保数据传输的隐私性、完整性和服务器身份的真实性

https=http+ssl

通过 Content-Type,服务器可识别请求体(Body)的格式(如 JSON、表单数据),客户端可解析响应数据的类型(如 HTML、图片)

例如:conn.send(b”HTTP/1.1 200 ok:zxj*content-type:text/html**hello world<>“)

再例如:‘HTTP/1.1 200 ok:zxj*content-type:application/json**’

api接口

在开发web应用中,有两种应用模式:

1.前后端不分离:客户端看到的内容和所有页面效果都是有服务端提供出来的

4

2.前后端分离:把前端的页面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)

前端形成一个独立的网站,服务端构成一个独立的网站

5

应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。

当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!

为了在团队内部形成共识,防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。

目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。它首次出现在2000年Roy Fielding的博士论文中。

RESTful是一种专门为Web开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。

关键:面向资源开发

这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改

请求方法 请求地址 后端操作
POST /student/ 增加学生
GET /student/ 获取所有学生
GET /student/1 获取id为1的学生
PUT /student/1 修改id为1的学生
DELETE /student/1 删除id为1的学生

restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。

fastapi快速开始

简单案例

安装:pip install fastapi

还需要一个ASGI服务器,生产环境使用Uvicorn:pip install uvicorn

ASGI(Asynchronous Server Gateway Interface )是一种异步服务器网关接口 ,为 Python Web 应用提供了标准接口,使其能够处理现代网络协议(如 WebSocket、HTTP/2 等)的异步请求。与传统的 WSGI 不同,ASGI 支持异步编程模型,允许单个请求处理多个事件(如长连接、双向通信),从而提升高并发场景下的性能

Uvicorn 是一个基于 ASGI 的高性能异步 Web 服务器,专为 Python 异步框架设计。

web应用程序=web框架+自己写的业务逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi import FastAPI#fastapi类

app= FastAPI()#创建一个fastapi实例

@app.get("/")
async def home():

return {"user_id":1001}

@app.get("/shop")
async def shop():
return {"shop_id":1002}

启动:uvicorn "04 fastapi_begin:app" --reload

也可以:

1
2
3
import uvicorn
if __name__ == "__main__":
uvicorn.run(app="04 fastapi_begin:app",port=8080,reload=True)

接口文档

6

修饰器(Decorator)是 Python 中一种动态修改函数或类行为的高级功能,本质上是一个函数或类,它接受目标函数或类作为参数,并返回包装后的新函数或类对象,从而在不修改原始代码 的前提下为对象添加额外功能

路径操作

路径操作修饰器

1
2
3
4
5
6
7
8
@app.get()
@app.post()
@app.put()
@app.patch()
@app.delete()
@app.options()
@app.head()
@app.trace()

路径操作修饰器参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''
tags为接口添加标签,用于在自动生成的文档
summary为接口添加描述
description为接口添加详细描述
response_description为接口返回值描述
deprecated为过时的接口
'''
@app.post("/post",
tags=["这是post方法"],
summary="这是post方法的描述",
description="这是post方法的详细描述",
response_description="这是post方法的返回值描述",
deprecated=True, # 过时的接口
)
def test():

include_router

文件路径如下

7

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI
import uvicorn

from apps.app01.shop import shop
from apps.app02.user import user

app= FastAPI()

app.include_router(shop,
prefix="/shop", # 路由前缀
tags=["购物中心接口"], # 标签
responses={200: {"description": "成功"}} # 响应描述
)
app.include_router(user,
prefix="/user", # 路由前缀
tags=["用户接口"], # 标签
responses={200: {"description": "成功"}} # 响应描述
)

if __name__ == "__main__":
uvicorn.run("main:app", port=8080, reload=True)

shop.py

1
2
3
4
5
6
7
8
9
10
11
from fastapi import APIRouter

shop=APIRouter()

@shop.get("/food")
def shop_food():
return {"food":"shop food"}

@shop.get("/drink")
def shop_drink():
return {"drink":"shop drink"}

user.py

1
2
3
4
5
6
7
8
9
10
11
from fastapi import APIRouter

user=APIRouter()

@user.post("/login")
def user_login():
return {"user":"user login"}

@user.post("/register")
def user_register():
return {"user":"user register"}

include_router 是 FastAPI 框架中用于整合路由的核心方法,其作用是将通过 APIRouter 定义的路由模块添加到主应用程序实例中,使这些路由在应用中生效。

8

请求与响应

4.1 路径参数

(1)基本用法

以使用与 Python 格式化字符串相同的语法来声明路径”参数”或”变量”:

1
2
3
4
@app.get("/user/{user_id}")
def get_user(user_id):
print(user_id, type(user_id))
return {"user_id": user_id}

路径参数 user_id 的值将作为参数 user_id 传递给你的函数。

(2)有类型的路径参数

你可以使用标准的 Python 类型标注为函数中的路径参数声明类型。

1
2
3
4
@app.get("/user/{user_id}")
def get_user(user_id: int):
print(user_id, type(user_id))
return {"user_id": user_id}

在这个例子中,user_id 被声明为 int 类型。

这将为你的函数提供编辑器支持,包括错误检查、代码补全等等。

(3)注意顺序

在创建路径操作时,你会发现有些情况下路径是固定的。

比如 /users/me,我们假设它用来获取关于当前用户的数据。

然后,你还可以使用路径 /user/{username} 来通过用户名获取关于特定用户的数据。

由于路径操作是按顺序依次运行的,你需要确保路径 /user/me 声明在路径 /user/{username} 之前。

如下

image-20250516193934740
image-20250516201128482

路由(Routing)是指在网络中选择数据传输路径的过程,其核心目标是将数据从源点高效、可靠地传输到目的地

cURL 是一个开源的命令行工具和跨平台的库(libcurl),用于基于 URL 语法在网络协议下进行数据传输。它支持多种协议(如 HTTP、HTTPS、FTP、SMTP 等),能够实现文件上传、下载以及与 Web 服务器的交互,常被开发者用于 API 测试、数据传输等场景

4.2 查询参数(请求参数)

路径函数中声明不属于路径参数的其他函数参数时,它们将被自动解释为查询字符串参数,就是 url?之后用 & 分割的 key-value 键值对

1
2
3
4
5
6
7
8
@app02.get("/jobs")
async def get_jobs(kind1: str, kind2: str, kind3: str):
#基于查询参数的值来执行不同的操作
return {
"kind1": kind1,
"kind2": kind2,
"kind3": kind3
}
image-20250516201707589

增加路径参数:kind1为路径参数

增加默认参数值

1
2
3
4
5
6
7
8
@app02.get("/jobs/{kind1}")
async def get_jobs(kind1: str, kind2: str="None", kind3: str="None"):#增加默认值,可选填
#基于查询参数的值来执行不同的操作
return {
"kind1": kind1,
"kind2": kind2,
"kind3": kind3
}

Request URL:

http://127.0.0.1:8080/app02/jobs/11?kind2=22&kind3=33

自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:

1.类型检查,防止运行时出现参数、返回值类型不符。

2.作为开发文档附加说明,方便使用者调用时传入和返回参数类型。

3.模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。

type hints主要是要指示函数的输入和输出的数据类型,数据类型在typing包中,基本类型有str list dict等等,

Type Hints 是 Python 3.5 引入的功能,通过类型注解增强代码的可读性和可维护性。它允许开发者为变量、函数参数、返回值等指定预期的数据类型,从而帮助静态类型检查工具(如 mypy)捕获潜在错误,并提升 IDE 的智能提示能力。例如:

1
2
def greet(name: str) -> str:
return f"Hello, {name}"

此处 name: str 表示参数需为字符串类型,-> str 表示返回值类型为字符串 。

Union是当有多种可能的数据类型时使用,比如函数有可能根据不同情况有时返回str或返回list,那么就可以写成Union[list, str]

从 Python 3.10 起,Union[X, Y] 可简写为 X | Y。例如 int | str 等价于 Union[int, str]

再例如:kind2:str|None=None

Optional是Union的一个简化,当数据类型中有可能是None时,比如有可能是str也有可能是None,则Optional[str],相当于Union[str, None]

4.3 请求体数据

当你需要将数据从客户端(例如浏览器)发送给 API 时,你将其作为「请求体」发送。请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。

FastAPI 基于 PydanticPydantic 主要用来做类型强制检查(校验数据)。不符合类型要求就会抛出异常。

对于 API 服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度,因为开发者再也不用自己写一行一行的做类型检查。

安装上手 pip install pydantic

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
from fastapi import APIRouter
from pydantic import BaseModel,Field,field_validator
from datetime import date
from typing import Union,Optional

app03 = APIRouter()

class Address(BaseModel):
province: str
city: str

class User(BaseModel):
#正则表达式
#username:str=Field(pattern="^[a-zA-Z0-9]{3,10}$",title="用户名",description="用户名长度在3-10之间,且只能包含字母和数字")
#name: str|None = None
name:str
age: int=Field(default=0,gt=0,lt=100)
birth:Union[date,None] = None
friends:list[int]=[]
description:Optional[str]=None
#嵌套
addr:Address|None = None

'''
field_validator 的第一个参数必须是 cls,因为它是类方法 (classmethod),用于在验证字段时访问模型类的上下文。
'''
@field_validator("name")
def name_must_alpha(cls,value):
'''
value.isalpha() 会检查字符串是否只由字母组成,如果是则返回 True,否则返回 False。
如果返回 False,assert 触发,会抛出 AssertionError,并显示错误信息 "name must be alpha"。
'''
assert value.isalpha(), "name must be alpha"
return value

#嵌套
class Data(BaseModel):
data:list[User]

@app03.post("/user")
async def user(user:User):
print(user,type(user))
return user

@app03.post("/data")
async def data(data:Data):
print(data,type(data))
return data

BaseModel专门用于数据验证、数据转换和序列化。在定义数据结构时继承自 BaseModel,可以:

  • 自动校验数据类型:根据类中字段的类型注解,自动校验输入数据是否符合预期类型。
  • 数据转换:可以自动将输入数据(例如 JSON 字符串)转换成相应的 Python 数据类型。
  • 序列化输出:支持将模型实例转换成 JSON、字典等格式,便于响应输出。

在 Pydantic 中,Field 用于为模型字段提供额外的信息,比如设置默认值、描述信息、约束条件(例如长度、范围等)或别名。这可以帮助自动生成 OpenAPI 文档、增强验证或对字段进行更细粒度的控制。

field_validator 是 Pydantic v2 中用于替代旧版 @validator 的新装饰器,专门用于为模型字段添加自定义验证逻辑。它通过更清晰的命名和更灵活的模式(如 mode="before"mode="after")提升代码可读性和验证逻辑的控制能力

field_validatormodel_validator区别

field_validator专门针对单个字段 进行验证,适用于需要校验特定字段的规则(如长度、格式、类型约束)。例如验证用户名长度

model_validator作用于整个模型实例 ,适用于需要跨字段验证或全局逻辑的场景。例如检查两次密码是否一致

4.4 form表单数据

OAuth2 规范的一种使用方式(密码流)中,需要将用户名、密码作为表单字段发送,而不是 JSON。

FastAPI 可以使用 Form 组件来接收表单数据,需要先使用 pip install python-multipart 命令进行安装。

1
2
3
4
5
6
7
@app04.post("/register")
async def data(username:str=Form(),password:str=Form()):
print(f"username: {username}, password: {password}")
return {
"username": username,
"password": password
}

发送post请求:form表单数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 目标 URL
url = "http://127.0.0.1:8080/app04/register"

# 表单数据(键值对)
data = {
"username": "test_user",
"password": "secure_password_123"
}

# 发送 POST 请求
response = requests.post(
url,
data=data
)

通过 requests.post() 的 data 参数传递表单数据,该参数接受字典或字符串格式的数据。requests 会自动将其编码为 application/x-www-form-urlencoded 格式

4.5 文件上传

导入必要库

1
2
3
from fastapi import File,UploadFile
import os
app05 = APIRouter()

通过字节上传

1
2
3
4
5
6
7
@app05.post("/file")
async def file(file: bytes = File(...)):
#适合小文件上传
return {
"filename": "file",
"size": len(file)
}

多文件上传

1
2
3
4
5
6
7
8
9
@app05.post("/files")
async def files(files: list[bytes] = File(...)):
#多文件上传
for file in files:
print(len(file))
return {
"filename": "files",
"size": len(files)
}

UploadFile上传,绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app05.post("/uploadfile")
async def upload_file(file: UploadFile= File(...)):
#适合大文件上传
# 获取当前文件的绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
print(base_dir)
img_dir = os.path.join(base_dir, "../imgs")
print(img_dir)
# 创建目录
path = os.path.join(img_dir, file.filename)
#path=os.path.join("../imgs",file.filename)
print(path)
with open(path, "wb") as f:
print("文件名:", file.filename)
for chunk in file.file:
f.write(chunk)

return {
"filename": file.filename
}

UploadFile是 FastAPI 提供的一个类,用于处理文件上传。与直接将文件内容读取为字节流(例如 bytes相比,UploadFile有以下优点:

  • 内存优化:它采用了文件对象的方式处理上传文件,不必将整个文件内容一次性加载到内存中,适合处理大文件。
  • 异步支持:支持异步操作,可以用异步方式读取文件内容,提高性能。
  • 文件元数据:提供文件名、内容类型等元数据信息,通过属性 filenamecontent_type 获取。
  • 文件接口:通过 file 属性获取一个类文件对象,可以像操作普通文件一样读取或保存上传的文件。

4.6 Request对象

有些情况下我们希望能直接访问 Request 对象。例如我们在路径操作函数中想获取客户端的 IP 地址,需要在函数中声明 Request 类型的参数,FastAPI 就会自动传递 Request 对象给这个参数,我们就可以获取到 Request 对象及其属性信息,例如 header、url、cookie、session 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app06.post("/items")
async def items(request: Request):
print("url:", request.url)
print("客户端ip:", request.client.host)
print("请求头:", request.headers)
print("客户端宿主",request.headers.get("user-agent"))
print("cookie:", request.cookies)
return {
"url": request.url,
"client_ip": request.client.host,
"headers": request.headers,
"user_agent": request.headers.get("user-agent"),
"cookies": request.cookies
}

4.7请求静态文件

在 Web 开发中,需要请求很多静态资源文件(不是由服务器生成的文件),如 css/js 和图片文件等。

main.py

1
2
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="statics"))#静态文件目录
image-20250517143857442
image-20250517143914772

静态网站

完全由静态文件(HTML、CSS、JavaScript)组成,内容固定不变,所有页面在开发时已预生成,无需动态计算或数据库支持

动态网站

内容根据用户请求实时生成,通常依赖数据库和服务器端编程(如PHP、Python、Node.js),能提供个性化和交互功能

StaticFiles是 FastAPI(实际来自 Starlette)提供的一个类,用于挂载和服务静态文件目录。 它的作用是让你可以通过 HTTP 路径直接访问服务器上的静态资源(如图片、CSS、JS 文件等)。

mount()方法用于将一个完整的应用或静态文件目录挂载到主 FastAPI 应用的某个路径下。这样,访问指定路径时,请求会被转发到挂载的应用或目录。

4.8 响应模型相关参数

response_model

response_model是 FastAPI 路由装饰器(如 @app.post@app.get 等)中的一个参数,用于指定接口响应的数据模型。它的作用是:

  • 自动校验和序列化:FastAPI 会根据你指定的 Pydantic 模型自动校验、过滤和格式化返回的数据。
  • 自动生成文档:接口文档会自动显示响应的数据结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str|None = None

class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str|None = None


@app07.post("/user02",response_model=UserOut)
async def create_user(user: UserIn):
# 这里可以进行一些处理,比如将用户信息存储到数据库中
return user

案例:

  • 注册功能
  • 输入账号、密码、昵称、邮箱,注册成功后返回个人信息
response_model_exclude_unset=True

通过上面的例子,我们学到了如何用 response_model 控制响应体结构,但是,如果它们实际上没有存储,则可能要从结果中忽略它们。例如,如果 model 在 NoSQL 数据库中具有很多可选属性,但是不想发送很长的 JSON 响应,其中包含默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Item(BaseModel):
name:str
description: str|None = None
price: float
tax:float=10.5
tags: list[str]|None = None

#模拟数据库
items={
"item01":Item(name="item01",price=10.5),
"item02":Item(name="item02",description="item02",price=20.5,tax=20.5,tags=["tag1","tag2"]),
"item03":Item(name="item03",description="item03",price=30.5,tax=30.5,tags=["tag1","tag2"]),
}

@app07.get("/items/{item_id}",response_model=Item,response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]

设置后返回为:

1
2
3
4
5
item01
{
"name": "item01",
"price": 10.5
}

当你设置 response_model_exclude_unset=True 时,返回的响应数据只包含被显式设置过的字段,没有被赋值的(即使用默认值且未传递的)字段不会出现在响应中。

其他参数

response_model_exclude_defaults 作用:排除所有值为默认值的字段。

response_model_exclude_none 作用:排除所有值为 None 的字段。

response_model_include 作用:只返回指定字段

response_model_exclude 作用:排除指定字段,不在响应中返回。

jinja2模板

要了解 jinja2,那么需要先理解模板的概念。模板在 Python 的 web 开发中广泛使用,它能够有效的将业务逻辑和页面逻辑分开,使代码可读性增强、并且更加容易理解和维护。

模板简单来说就是一个其中包涵占位变量表示动态的部分的文件,模板文件在经过动态赋值后,返回给用户。

jinja2 是 Flask 作者开发的一个模板系统,起初是仿 django 模板的一个模板引擎,为 Flask 提供模板支持,由于其灵活,快速和安全等优点被广泛使用。

在 jinja2 中,存在三种语法:

  1. 变量取值 {{ }}
  2. 控制结构 {% %}

应用于前后端不分离,模板html+数据库,返回动态网站

5.1 变量

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from fastapi.templating import Jinja2Templates

templates=Jinja2Templates(directory="templates")

@app.get("/index")
def index(request: Request):
#数据库
name="World"
books=["Python", "Java", "C++"]
user={"name":"Tom", "age":18}

return templates.TemplateResponse(
"index.html",#模板文件
{
"request": request,# FastAPI需要一个request对象
"name": name,
"books": books,
"user": user
}#context上下文对象
)

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<h1>Hello, {{ name }}!</h1>
<p>Your favorite books are:</p>
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
</body>

</html>
image-20250517200643492

5.2 过滤器

变量可以通过“过滤器”进行修改,过滤器可以理解为是 jinja2 里面的内置函数和字符串处理函数。常用的过滤器有:

过滤器名称 说明
capitalize 把值的首字母转换成大写,其他字母转换为小写
lower 把值转换成小写形式
title 把值中每个单词的首字母都转换成大写
trim 把值的首尾空格去掉
striptags 渲染之前把值中所有的 HTML 标签都删掉
join 拼接多个值为字符串
round 默认对数字进行四舍五入,也可以用参数进行控制
safe 渲染时值不转义

那么如何使用这些过滤器呢?只需要在变量后面使用管道 (|) 分割,多个过滤器可以链式调用,前一个过滤器的输出会作为后一个过滤器的输入。

例如:<h1>Hello, {{ name|upper }}!</h1> <li>{{ book|title }}</li>

5.3 控制结构

jinja2中的if语句类似与Python的if语句,它也具有单分支,多分支等多种结构,不同的是,条件语句不需要使用冒号结尾,而结束控制语句,需要使用endif关键字

1
2
3
4
5
6
7
8
9
10
11
12
<p>影视区</p>
{% if age >= 18 %}
<ul>
<li>成人影片</li>
<li>成人游戏</li>
</ul>
{% else %}
<ul>
<li>儿童影片</li>
<li>儿童游戏</li>
</ul>
{% endif %}

jinja2中的for循环用于迭代Python的数据类型,包括列表、元组和字典。在jinja2中不存在while循环。

1
2
3
{% for book in books %}
<li>{{ book|title }}</li>
{% endfor %}

ORM操作

在大型的 Web 开发中,我们肯定会用到数据库操作,那么 FastAPI 也支持数据库的开发,你可以用 PostgreSQL、MySQL、SQLite、Oracle 等。本文用 SQLite 为例。我们看下在 FastAPI 是如何操作设计数据库的。

FastAPI 是一个很优秀的框架,但是缺少一个合适的 ORM,官方代码里面使用的是 SQLAlchemy,Tortoise ORM 是受 Django 启发的易于使用的异步 ORM(对象关系映射器)。

Tortoise ORM 目前支持以下数据库:

  • PostgreSQL >= 9.4(使用 asyncpg)
  • SQLite(使用 aiosqlite)
  • MySQL/MariaDB(使用 aiomysql 或使用 asyncmy)

安装:pip install tortoise-orm

6.1 创建模型

1. 一对一关系(One-to-One)

  • 定义 :一张表中的一条记录仅关联另一张表中的一条记录。
  • 示例 :用户表(User)与身份证信息表(IDCard),一个用户仅对应一张身份证信息。

2. 一对多关系(One-to-Many)

  • 定义 :一张表中的一条记录关联另一张表中的多条记录。
  • 示例 :班级表(Class)与学生表(Student),一个班级包含多个学生,但每个学生只能属于一个班级。

3. 多对多关系(Many-to-Many)

  • 定义 :两张表中的记录可以互相关联多条记录,通常通过中间表实现。
  • 示例 :学生表(Student)与课程表(Course),一个学生可选修多门课程,一门课程也可被多个学生选修。

4. 自引用关系(Self-Referencing)

  • 定义 :表内的记录通过字段关联自身,形成层级或树状结构。
  • 示例 :员工表(Employee),每个员工可能有直属上级(另一个员工)。

5. 继承关系(Inheritance)

  • 定义 :基于面向对象的继承概念,子表继承父表的字段和约束。
  • 示例 :用户表(User)作为基表,管理员表(Admin)和普通用户表(RegularUser)继承其字段(如用户名、密码)。
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
from tortoise.models import Model
from tortoise import fields
'''
Model 是所有数据模型的基类,通过继承 Model 可定义数据库表的结构。每个 Model 子类对应一张数据库表,其类属性定义了表的字段(列)及其约束。
fields 提供了多种字段类型,用于定义数据库表的列及其约束。
'''
class Student(Model):
id = fields.IntField(pk=True)#该字段会被指定为模型的主键
name = fields.CharField(max_length=50,description="姓名")
pwd = fields.CharField(max_length=50,description="密码")
sno = fields.IntField(description="学号")
#一对多关系
Class_id = fields.ForeignKeyField(
"models.Class",#关联的模型类
related_name="students",#反向查询时的名称
on_delete=fields.CASCADE,#级联删除
description="班级"
)
#多对多关系
Course_id = fields.ManyToManyField(
"models.Course",#关联的模型类
related_name="students",#反向查询时的名称
description="课程"
)

class Course(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=50,description="课程名称")
teacher = fields.CharField(max_length=50,description="授课老师")
#一对多关系
teacher_id = fields.ForeignKeyField(
"models.Teacher",#关联的模型类
related_name="courses",#反向查询时的名称
on_delete=fields.CASCADE,#级联删除
description="老师"
)

class Class(Model):
id = fields.IntField(pk=True)
name= fields.CharField(max_length=50,description="班级名称")

class Teacher(Model):
id =fields.IntField(pk=True)
name = fields.CharField(max_length=50,description="老师姓名")
pwd = fields.CharField(max_length=50,description="密码")
sno = fields.IntField(description="工号")

ORM(Object Relational Mapping,对象关系映射)是一种程序设计技术,主要用于实现面向对象编程语言关系型数据库 之间的数据转换。其核心思想是通过对象模型与数据库表结构的映射,将数据库操作转化为面向对象的操作,从而简化开发流程并提升代码的可维护性

Tortoise ORM 是一款专为 Python 异步环境设计的轻量级对象关系映射(ORM)框架,其设计灵感来源于 Django ORM,但专注于异步编程场景,适用于 FastAPI、Sanic 等基于 asyncio 的现代 Web 框架。

关系型数据库与非关系型数据库

关系型数据库 以表格形式存储数据,数据按行和列组织,列代表属性(字段),行代表记录。例如,用户表可能包含 idnameemail 等列,每行对应一个用户记录。这种结构化设计支持严格的模式约束(Schema)17。

典型代表 :MySQL、Oracle、PostgreSQL。

非关系型数据库(NoSQL) 采用非结构化或半结构化存储,常见的类型包括:

  • 文档型 (如 MongoDB):以 JSON 或 BSON 格式存储数据。

  • 键值型 (如 Redis):通过键直接访问值。

  • 列存储 (如 Cassandra):按列而非行组织数据。

  • 图数据库

    (如 Neo4j):以节点和边表示数据关系

6.2 aerich迁移工具

docker 安装 mysql

拉取 MySQL 镜像:docker pull mysql

运行 MySQL 容器:

1
docker run --name fastapi -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql

-p表示端口映射 –restart=always表示容器退出时总是重启 –name表示容器命名 –privileged=true表示赋予容器权限修改宿主文件权利 -v /home/mysql/log:/var/log/mysql表示容器日志挂载到宿主机 -v /home/mysql/data:/var/lib/mysql表示容器存储文件挂载到宿主机 -v /home/mysql/conf/my.cnf:/etc/mysql/my.cnf表示容器配置文件挂载到宿主机 -e MYSQL_ROOT_PASSWORD=a12bCd3_W45pUq6表示设置mysql的root用户密码,建议用强密码 -d表示后台运行

启动这个 MySQL 容器:docker start fastapi

进入 MySQL 容器:docker exec -it fastapi bash

这条命令的作用是:

  • docker exec:在已运行的 Docker 容器中执行命令。
  • -it-i 表示交互式操作,-t 分配一个伪终端(让你像在终端一样操作)。
  • fastapi:这是你要进入的容器名称(你的 MySQL 容器名)。
  • bash:在容器内启动 bash shell。

这条命令会让你进入名为 fastapi 的容器,并获得一个 bash 命令行界面,就像登录到一台 Linux 服务器一样,可以在里面执行各种命令(比如登录 MySQL、查看日志等)。

登录 MySQL:mysql -u root -p

从主机直接连接:mysql -h 127.0.0.1 -P 3306 -u root -p

配置文件
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
TORTOISE_ORM = {
"connections": {
"default": {
"engine":'tortoise.backends.mysql',#选择数据库引擎,mysql
"credentials": {
"host": "localhost",#数据库地址
"port": 3306,#数据库端口
"user": "root",#数据库用户名
"password": "root",#数据库密码
"database": "fastapi_db",#数据库名称
'charset': "utf8mb4",#数据库编码
'echo': True,#是否打印sql语句
'minsize': 1,#连接池最小连接数
'maxsize': 5#连接池最大连接数
}
}
},
"apps": {
"models": {
#db.models是我们自己定义的模型类,models在db文件夹下
"models": ["db.models","aerich.models"],
"default_connection": "default"
}
},
'use_tz': False,#是否使用时区
'timezone': 'Asia/Shanghai',#时区
"generate_schemas": True,
"add_exception_handlers": True
}

1.初始化配置,只需要使用一次

aerich 是一种 ORM 迁移工具,需要结合 tortoise 异步 orm 框架使用。安装 aerich

pip install aerich

aerich init -t settings.TORTOISE_ORM # TORTOISE_ORM 配置的位置

初始化完会在当前目录生成一个文件:pyproject.toml 和一个文件夹:migrations

  • pyproject.toml:保存配置文件路径,低版本可能是 aerich.ini
  • migrations:存放迁移文件
2.初始化数据库,一般情况下只用一次

aerich init-db

image-20250521105044582
3.更新模型并进行迁移

修改model类,重新生成迁移文件

aerich migrate

image-20250521111024194
4.重新执行迁移,写入数据库

aerich upgrade

image-20250521111131931
5.回到上一个版本

aerich downgrade

6.查看历史迁移记录

aerich history

register_tortoise 是 Tortoise ORM 提供的一个工具函数,用于在 FastAPI 等异步框架中快速集成和管理 Tortoise ORM 的生命周期(如启动时初始化数据库连接,关闭时释放资源)。其核心作用是简化 Tortoise ORM 的配置和自动化管理,开发者只需一行代码即可完成复杂的初始化流程

6.3 ORM查询操作

api.stud

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
from fastapi import APIRouter
#导入数据库
from db.models import *

student_api=APIRouter()

@student_api.get("")
async def get_students():
"""
#查询所有学生信息
students= await Student.all()#获取所有学生信息
for student in students:
print(student.id,student.name)

#过滤查询filter
students= await Student.filter(name__contains="张").all()
for student in students:
print(student.id,student.name)

#get学生信息
stu = await Student.get(id=1)
print(stu.id,stu.name)

#模糊查询
#最大值
stu =await Student.filter(sno__gt=1000).all()
#最小值
#stu = await Student.filter(sno__lt=1000).all()
#范围查询
#stu = await Student.filter(sno__range=(1000,2000)).all()

#values查询
stu = await Student.all().values("sno","name")
"""

return{
"操作":"获取所有学生信息"
}

@student_api.post("")
async def create_student():
return{
"操作":"创建学生信息"
}

@student_api.get("/{student_id}")
async def get_student(student_id:int):
return{
"操作":f"获取学生信息,ID:{student_id}"
}

@student_api.put("/{student_id}")
async def update_student(student_id:int):
return{
"操作":f"更新学生信息,ID:{student_id}"
}

在 FastAPI 和 Tortoise ORM 中,asyncawait 用于异步编程,主要原因如下:

  1. 异步 I/O 操作 数据库查询(如 Student.all())是耗时的 I/O 操作。使用 async/await 可以在等待数据库响应时,不阻塞主线程,提高应用的并发性能。
  2. FastAPI 支持异步路由 FastAPI 支持异步(async def)的路由函数,这样可以充分利用 Python 的异步特性,提升 Web 服务的吞吐量。
  3. Tortoise ORM 的方法是异步的 Tortoise ORM 的数据库操作方法(如 .all().create() 等)本身就是异步方法,必须用 await 调用,并且所在函数必须用 async def 声明。

中间件

FastAPI 的「中间件(middleware)」就是 在请求进入路由函数之前、响应离开路由函数之后 插入的 通用处理逻辑

image-20250815144154925

什么时候需要中间件

真实场景举例 —— 统一鉴权 + 日志

需求 • 所有 API(/user、/order、/admin …)都必须验证 JWT; • 无论成功或失败,都记录一条结构化日志(URL、耗时、用户 ID、响应码); • 鉴权失败直接 401,不再进入任何路由。

实战

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
from fastapi import FastAPI, Request
import uvicorn
app = FastAPI()

@app.middleware("http") # 注册一个全局中间件
async def mid1(request: Request, call_next):
print("mid1 request")
response = await call_next(request) #先让请求继续往后走
print("mid1 respones")
return response

@app.middleware("http")
async def mid2(request: Request, call_next):
print("mid2 request")
response = await call_next(request)
print("mid2 respones")
return response

@app.get("/") # 任意路由
def home():
print("home")
return {"msg": "ok"}


if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)

注意中间件添加的顺序

FastAPI/Starlette 把后添加的中间件包在最外层(洋葱最外层)

所以 mid2 在外层,请求先打印

1
2
3
4
5
mid2 request
mid1 request
home
mid1 response
mid2 response

cors组件

CORS(Cross-Origin Resource Sharing,跨源资源共享) 一句话:浏览器为了安全,默认禁止网页去“别的域名/端口/协议”拿数据;CORS 是一套 HTTP 机制,让服务器告诉浏览器“我允许谁来拿、拿什么、怎么拿”。

同源与跨域

同源(Same-Origin)

浏览器把下面三个部分合称 “源”(origin):

  1. 协议(http: / https:
  2. 域名(example.com / sub.example.com 算不同)
  3. 端口(:80 / :8080

只有当 协议 + 域名 + 端口 都完全一致时,才叫同源。 此时前端 JS 可以无限制地访问该源下的资源。

跨域(Cross-Origin)

只要 协议、域名、端口 中的任意一个不同,就是跨域

正常来说,服务器为了保护数据,会拒绝跨域的响应,但是通过cors可以允许跨域,因此,CORS 不是“绕过”安全限制,而是服务器主动声明的“安全白名单”。

1
2
3
4
5
6
7
8
9
# 为 FastAPI 应用添加 CORS(跨源资源共享)中间件
# 允许浏览器跨域访问本服务,开发阶段常用;生产环境请收窄范围
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源(生产建议改成具体域名列表,如 ["https://foo.com"])
allow_credentials=False, # 是否允许携带 Cookie/Authorization;True 时 origins 不能为 "*"
allow_methods=["GET"], # 允许的 HTTP 方法;["*"] 表示全部
allow_headers=["*"], # 允许的自定义请求头;["*"] 表示全部
)

知识点

端口查询

查看所有端口占用情况:netstat -ano

查询特定端口是否被占用:netstat -ano | findstr 8080

使用 taskkill 命令强制结束进程:taskkill /PID 进程ID /F

通过 PID 查找进程:tasklist | findstr PID

获取绝对路径

获取当前文件的绝对路径:base_dir = os.path.dirname(os.path.abspath(__file__))

拼接路径:img_dir = os.path.join(base_dir, "../imgs")

mysql部分指令

查看数据库列表SHOW DATABASES;

选择数据库USE 数据库名;

删除数据库DROP DATABASE 数据库名;

创建数据库CREATE DATABASE 数据库名;

登录 MySQLmysql -u root -p

退出exit

docker部分指令

linux安装dockersudo apt-get update && sudo apt-get install docker.io

查看 Docker 版本信息docker version

查看镜像docker images

查看所有的容器docker ps -a

systemctlsystemd 系统和服务管理器的核心工具,用于管理系统和服务的状态及配置。

mysql-client 是 MySQL 数据库的命令行客户端工具。它允许你通过命令行连接和操作 MySQL 数据库服务器,比如执行 SQL 查询、管理数据库和用户等。

常用命令格式如下:mysql -h 主机地址 -P 端口号 -u 用户名 -p

你可以在终端输入以下命令来检查是否已安装 mysql-clientmysql --version

可以使用以下命令安装:sudo apt-get update sudo apt-get install mysql-client

sudo apt-get update 这个命令的作用是更新本地软件包列表

停止并删除容器docker stop fastapi docker rm fastapi

参考资料

fastapi一个项目FastAPI进阶_哔哩哔哩_bilibili

教程fastapi框架快速学习_哔哩哔哩_bilibili

fastapi相关知识的补充Python 异步编程 - 搞明白 async, await (继续解释 yield)_哔哩哔哩_bilibili

1912

2025年的生日,与李哥在百家湖1912聚餐,李哥请我吃的铁板烧,超级美味

1745080784805
1745080784820
1745080784834
1745080784848
1745080784862
1745080784789

前言

我是一个很珍惜回忆的人,但是任何回忆都有忘却的那一天,所以我能做的就是尽可能把他ji’lu

我想听他扫弦的声音

南京1701livehouse

1745077840515
1745077840490
1745077840469
1745077840480

梦龙演唱会

4.6 Imagine Dragons 杭州 真的太嗨太嗨了,内场氛围巨好无比,所有人都在合唱,超值啊! 再记录一下这次比较特别的体验,在小红书找到了一个自驾去看演唱会的,五个人一辆车边走边聊边听歌,也是很不错啊

1745078837710
1745078837725
1745078837752
1745078837739
1745078837778
0%