复习:在前面我们已经学习了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')

复习:回顾学习完第一章,我们对泰坦尼克号数据有了基本的了解,也学到了一些基本的统计方法,第二章中我们学习了数据的清理和重构,使得数据更加的易于理解;今天我们要学习的是第二章第三节:数据可视化,主要给大家介绍一下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/第二章:第四节数据可视化-课程_10_0.png)

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

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/第二章:第四节数据可视化-课程_23_1.png)

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

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/第二章:第四节数据可视化-课程_27_2.png)

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等。

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

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

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

1
2
3
# 导入基本库
import numpy as np
import pandas as pd
1
2
3
# 载入上一个任务人保存的文件中:result.csv,并查看这个文件
text = pd.read_csv('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 第二章:数据重构

第一部分:数据聚合与运算

2.6 数据运用

2.6.1 任务一:通过教材《Python for Data Analysis》P303、Google or anything来学习了解GroupBy机制

1
2
3
4
5
6
7
8
#写入心得
'''
GroupBy机制是Pandas中用于数据分组与聚合的核心操作,其本质是遵循"Split-Apply-Combine"模式:

Split:按指定键(列、函数、数组等)将数据分割成多个子集
Apply:对每个子集独立应用聚合函数(如mean/max)、转换函数(如标准化)或过滤操作
Combine:将结果合并为新的数据结构
'''

2.4.2:任务二:计算泰坦尼克号男性与女性的平均票价

1
2
3
4
5
# 写入代码
df = text['Fare'].groupby(text['Sex'])
print(df.groups)
means = df.mean()
means
{'female': [1, 2, 3, 8, 9, 10, 11, 14, 15, 18, 19, 22, 24, 25, 28, 31, 32, 38, 39, 40, 41, 43, 44, 47, 49, 52, 53, 56, 58, 61, 66, 68, 71, 79, 82, 84, 85, 88, 98, 100, 106, 109, 111, 113, 114, 119, 123, 128, 132, 133, 136, 140, 141, 142, 147, 151, 156, 161, 166, 167, 172, 177, 180, 184, 186, 190, 192, 194, 195, 198, 199, 205, 208, 211, 215, 216, 218, 229, 230, 233, 235, 237, 240, 241, 246, 247, 251, 254, 255, 256, 257, 258, 259, 264, 268, 269, 272, 274, 275, 276, ...], 'male': [0, 4, 5, 6, 7, 12, 13, 16, 17, 20, 21, 23, 26, 27, 29, 30, 33, 34, 35, 36, 37, 42, 45, 46, 48, 50, 51, 54, 55, 57, 59, 60, 62, 63, 64, 65, 67, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 86, 87, 89, 90, 91, 92, 93, 94, 95, 96, 97, 99, 101, 102, 103, 104, 105, 107, 108, 110, 112, 115, 116, 117, 118, 120, 121, 122, 124, 125, 126, 127, 129, 130, 131, 134, 135, 137, 138, 139, 143, 144, 145, 146, 148, 149, 150, 152, 153, 154, 155, ...]}

Sex
female    44.479818
male      25.523893
Name: Fare, dtype: float64

在了解GroupBy机制之后,运用这个机制完成一系列的操作,来达到我们的目的。

下面通过几个任务来熟悉GroupBy机制。

2.4.3:任务三:统计泰坦尼克号中男女的存活人数

1
2
3
# 写入代码
survived_sex = text['Survived'].groupby(text['Sex']).sum()
survived_sex.head()
Sex
female    233
male      109
Name: Survived, dtype: int64

2.4.4:任务四:计算客舱不同等级的存活人数

1
2
3
# 写入代码
survived_pclass = text['Survived'].groupby(text['Pclass'])
survived_pclass.sum()
Pclass
1    136
2     87
3    119
Name: Survived, dtype: int64

提示:】表中的存活那一栏,可以发现如果还活着记为1,死亡记为0

思考】从数据分析的角度,上面的统计结果可以得出那些结论

【思考】从任务二到任务三中,这些运算可以通过agg()函数来同时计算。并且可以使用rename函数修改列名。你可以按照提示写出这个过程吗?

1
2
3
4
5
6
7
#思考心得
'''
agg() 函数是 Pandas 中用于对分组后的数据 同时执行多个聚合操作 的核心工具,其全称为 Aggregate(聚合)。它允许你对不同列应用不同的聚合函数,并支持自定义函数,极大提升数据分析效率。
'''

text.groupby('Sex').agg({'Fare': 'mean', 'Pclass': 'count'}).rename(columns=
{'Fare': 'mean_fare', 'Pclass': 'count_pclass'})

mean_fare count_pclass
Sex
female 44.479818 314
male 25.523893 577

2.4.5:任务五:统计在不同等级的票中的不同年龄的船票花费的平均值

1
2
3
4
# 写入代码
temp=text.groupby(['Pclass','Age'])['Fare']
temp.groups
temp.mean().head()
Pclass  Age  
1       0.92     151.5500
        2.00     151.5500
        4.00      81.8583
        11.00    120.0000
        14.00    120.0000
Name: Fare, dtype: float64

2.4.6:任务六:将任务二和任务三的数据合并,并保存到sex_fare_survived.csv

1
2
3
4
# 写入代码
result = pd.merge(means,survived_sex,on='Sex')
result
result.to_csv('sex_fare_survived.csv')

2.4.7:任务七:得出不同年龄的总的存活人数,然后找出存活人数最多的年龄段,最后计算存活人数最高的存活率(存活人数/总人数)

1
2
3
4
# 写入代码
survived_age = text['Survived'].groupby(text['Age']).sum()
survived_age.head()

Age
0.42    1
0.67    1
0.75    2
0.83    2
0.92    1
Name: Survived, dtype: int64
1
2
# 写入代码
survived_age[survived_age.values==survived_age.max()]
Age
24.0    15
Name: Survived, dtype: int64
1
2
_sum = text['Survived'].sum()
print(_sum)
342
1
2
3
4
5
print("sum of person:"+str(_sum))

precetn =survived_age.max()/_sum

print("最大存活率:"+str(precetn))
sum of person:342
最大存活率:0.043859649122807015

前言

代码仓库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\r\nserver:zxj\r\ncontent-type:text/html\r\n\r\n

hello world<\h1>”)

再例如:‘HTTP/1.1 200 ok\r\nserver:zxj\r\ncontent-type:application/json\r\n\r\n{“user_id”:zxj}’

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

前言

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

1912

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

1745080784805

1745080784820

1745080784834

1745080784848

1745080784862

1745080784789

梦龙演唱会

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

1745078837710

1745078837725

1745078837752

1745078837739

1745078837778

橘子海

橘子海,现场超超超超级赞,嗨到爆,完全超出预期

Give me the faith that we broke

请重拾我们背叛过的誓言

Reminds me the verse that we spoke

不要让我遗忘共同诵读过的诗篇

There is no chance for start it over

一切已经永远无法重来

Back to the check point be my lover

回不去那个你我还是“我们”的存盘点

1745079894738

1745079894693

1745079894721

1745079894676

1745079894659

1745079894631

1745079894645

以下是 Hexo 常用的指令整理,方便快速查阅:


基础操作

  1. 初始化博客

    1
    hexo init [文件夹名]  # 创建新博客(不指定文件夹则在当前目录生成)
  2. 安装依赖

    1
    npm install           # 安装 Hexo 核心依赖(初始化后可能需要执行)
  3. 本地预览

    1
    hexo server           # 启动本地服务器(默认端口 4000),缩写:hexo s
  4. 生成静态文件

    1
    hexo generate         # 生成 public 文件夹的静态文件,缩写:hexo g
  5. 部署到服务器

    1
    hexo deploy           # 部署到 GitHub Pages 或其他平台,缩写:hexo d

文章与页面

  1. 新建文章

    1
    hexo new "文章标题"    # 生成新文章(Markdown 文件),缩写:hexo n
  2. 新建页面

    1
    hexo new page "页面名" # 创建自定义页面(如 about、tags)

清理与调试

  1. 清理缓存

    1
    hexo clean            # 删除生成的 public 和缓存文件(修改主题后建议执行)
  2. 查看帮助

    1
    hexo help             # 查看所有指令说明

组合指令(高效操作)

  • 生成并部署

    1
    2
    hexo g -d          # 先生成静态文件,再部署(等同 hexo generate && hexo deploy)
    hexo d -g # 同上,顺序不影响结果
  • 生成并预览

    1
    hexo s -g          # 先生成文件,再启动本地服务器

注意事项

  • 部署前配置:需在 _config.yml 中设置 deploy 参数(如 GitHub 仓库地址)。
  • 安装部署插件:首次部署需运行 npm install hexo-deployer-git
  • 主题安装:将主题克隆到 themes/ 文件夹后,在配置文件中指定主题名称。

如果需要更详细的操作说明,可以补充具体场景(如更换主题、设置分类等)!

hexo d上传失败,网络连接问题解决方案

原因:Clash 虽然开启了代理,但 Git 默认不会走这个代理,导致连接 GitHub 时失败

image-20250722204420664

解决方案:

查看端口号

image-20250722204512612

查看git代理

1
2
git config --global --get http.proxy
git config --global --get https.proxy

更改代理

1
2
git config --global http.proxy socks5://127.0.0.1:1080
git config --global https.proxy socks5://127.0.0.1:1080

参考教程

HEXO系列教程 | 使用GitHub部署静态博客HEXO | 小白向教程 – 夜梦星尘の折腾日记

Github王炸功能Pages,免费免服务器上线网站,详细教程_哔哩哔哩_bilibili

0%