Python 基础进阶(三):从函数、类到 asyncio 与 FastAPI 后端开发实战

目录

一、函数:将重复逻辑封装起来

[1.1 默认参数](#1.1 默认参数)

[1.2 关键字参数](#1.2 关键字参数)

[1.3 可变参数 *args 与 **kwargs](#1.3 可变参数 *args 与 **kwargs)

[1.4 小案例:订单金额计算](#1.4 小案例:订单金额计算)

二、类与对象:让数据和行为放在一起

[2.1 init 是什么](#2.1 init 是什么)

[2.2 类和对象的关系](#2.2 类和对象的关系)

[2.3 小案例:简单任务类](#2.3 小案例:简单任务类)

三、装饰器:不修改原函数也能增强功能

[3.1 最简单的装饰器](#3.1 最简单的装饰器)

[3.2 支持任意参数的装饰器](#3.2 支持任意参数的装饰器)

[3.3 FastAPI 中的装饰器](#3.3 FastAPI 中的装饰器)

四、迭代器与生成器

[4.1 什么是迭代](#4.1 什么是迭代)

[4.2 什么是迭代器](#4.2 什么是迭代器)

[4.3 自定义迭代器](#4.3 自定义迭代器)

[4.4 什么是生成器](#4.4 什么是生成器)

[4.5 小案例:分批读取数据](#4.5 小案例:分批读取数据)

[五、asyncio:Python 的异步编程基础](#五、asyncio:Python 的异步编程基础)

[5.1 同步执行的问题](#5.1 同步执行的问题)

[5.2 异步执行](#5.2 异步执行)

[5.3 async 与 await](#5.3 async 与 await)

[5.4 一个重要误区](#5.4 一个重要误区)

六、FastAPI:从零写一个任务管理后端

[6.1 安装 FastAPI](#6.1 安装 FastAPI)

[6.2 第一个 FastAPI 接口](#6.2 第一个 FastAPI 接口)

[6.3 路径参数](#6.3 路径参数)

[6.4 查询参数](#6.4 查询参数)

[七、FastAPI 小项目:任务管理 API](#七、FastAPI 小项目:任务管理 API)

[7.1 Pydantic 模型的作用](#7.1 Pydantic 模型的作用)

[7.2 测试新增任务接口](#7.2 测试新增任务接口)

[7.3 测试修改任务状态](#7.3 测试修改任务状态)

[7.4 HTTPException 的作用](#7.4 HTTPException 的作用)

[八、FastAPI 与 asyncio 的关系](#八、FastAPI 与 asyncio 的关系)

总结

在前两篇中,我们学习了 Python 常用容器,例如列表、元组、字符串、集合和字典。

容器解决的是"如何保存一批数据"的问题;而接下来,我们需要学习"如何组织和处理这些数据"。

本篇将继续学习 Python 函数、类、装饰器、迭代器、生成器、asyncio,并重点入门 FastAPI 后端开发。

一、函数:将重复逻辑封装起来

函数可以理解为一段可重复使用的代码。例如,我们需要多次计算商品订单总价,如果每次都手动写计算逻辑,代码会重复且不利于维护。

python 复制代码
price = 99.9
count = 2
total = price * count

print(total)

可以把它封装成函数:

python 复制代码
def calculate_total(price, count):
    return price * count

result = calculate_total(99.9, 2)

print(result)

输出:

复制代码
199.8

函数由以下部分组成:

python 复制代码
def 函数名(参数):
    函数体
    return 返回值

其中:

  • def:定义函数的关键字;
  • 函数名:建议使用小写字母和下划线;
  • 参数:函数需要接收的数据;
  • return:将结果返回给调用者。

1.1 默认参数

有时函数的某个参数通常使用固定值,可以给它设置默认值。

python 复制代码
def greet(name, message="你好"):
    return f"{message},{name}!"


print(greet("张三"))
print(greet("李四", "早上好"))

输出:

复制代码
你好,张三!
早上好,李四!

注意:带默认值的参数应该放在普通参数后面。

python 复制代码
# 正确
def create_user(name, age=18):
    pass
复制代码
# 错误
def create_user(age=18, name):
    pass

1.2 关键字参数

调用函数时,可以显式指定参数名称。

python 复制代码
def create_user(name, age, city):
    return {
        "name": name,
        "age": age,
        "city": city
    }


user = create_user(
    city="西安",
    name="小明",
    age=20
)

print(user)

输出:

复制代码
{'name': '小明', 'age': 20, 'city': '西安'}

关键字参数的好处是:参数顺序可以改变,代码可读性也更好。

1.3 可变参数 *args**kwargs

当函数接收的参数个数不固定时,可以使用可变参数。*numbers 会把传入的多个位置参数收集为元组

python 复制代码
def calculate_sum(*numbers):
    return sum(numbers)


print(calculate_sum(1, 2, 3))
print(calculate_sum(10, 20, 30, 40))

kwargs 会把多个关键字参数收集为字典** 。

python 复制代码
def print_user_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")


print_user_info(
    name="小明",
    age=20,
    city="西安"
)

这和前面学习的容器知识正好对应:

复制代码
*args   -> 元组 tuple
**kwargs -> 字典 dict

1.4 小案例:订单金额计算

python 复制代码
def calculate_order_total(price, count, discount=1.0):
    """计算订单最终金额。

    price: 商品单价
    count: 商品数量
    discount: 折扣,默认不打折
    """
    original_total = price * count
    final_total = original_total * discount

    return round(final_total, 2)  #保留两位小数


total = calculate_order_total(
    price=299.0,
    count=2,
    discount=0.85
)

print(f"订单最终金额:{total} 元")

输出:

复制代码
订单最终金额:508.3 元

二、类与对象:让数据和行为放在一起

函数适合处理单一逻辑。但当一个事物既有数据、又有行为时,使用类会更合适。

例如,一个学生拥有:

复制代码
姓名
年龄
分数

同时还可以:

复制代码
自我介绍
修改分数
判断是否及格

这时就可以使用类。

python 复制代码
class Student:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

    def introduce(self):
        print(f"大家好,我叫 {self.name},今年 {self.age} 岁。")

    def is_passed(self):
        return self.score >= 60

    def update_score(self, new_score):
        self.score = new_score

创建对象:

复制代码
student = Student("小明", 20, 88)

student.introduce()

print(student.is_passed())

student.update_score(95)

print(student.score)

输出:

复制代码
大家好,我叫 小明,今年 20 岁。
True
95

2.1 __init__ 是什么

__init__ 是初始化方法。当我们创建对象时:

复制代码
student = Student("小明", 20, 88)

Python 会自动调用:

复制代码
__init__(self, "小明", 20, 88)

其中:

复制代码
self.name = name

表示将传入的 name 保存到当前对象中。self 可以理解为"当前创建出来的对象本身"。

2.2 类和对象的关系

复制代码
Student 类
   ↓
创建对象
   ↓
student1、student2、student3

例如:

python 复制代码
student1 = Student("小明", 20, 88)
student2 = Student("小红", 21, 56)

print(student1.name)
print(student2.name)

print(student1.is_passed())
print(student2.is_passed())

每个对象都有自己的属性数据。

2.3 小案例:简单任务类

后面使用 FastAPI 编写后端时,也会频繁处理"任务""用户""订单"等对象。

python 复制代码
class Task:
    def __init__(self, task_id, title, completed=False):
        self.task_id = task_id
        self.title = title
        self.completed = completed

    def finish(self):
        self.completed = True

    def to_dict(self):
        return {
            "id": self.task_id,
            "title": self.title,
            "completed": self.completed
        }


task = Task(1, "学习 FastAPI")

print(task.to_dict())

task.finish()

print(task.to_dict())

输出:

python 复制代码
{'id': 1, 'title': '学习 FastAPI', 'completed': False}
{'id': 1, 'title': '学习 FastAPI', 'completed': True}

三、装饰器:不修改原函数也能增强功能

装饰器是 Python 中一个非常重要的功能。它可以在不修改原函数代码的前提下,为函数增加额外能力。

常见使用场景:

  • 记录日志;
  • 统计函数执行时间;
  • 登录校验;
  • 权限校验;
  • 请求参数校验;
  • 缓存处理;
  • Web 接口路由注册。

3.1 最简单的装饰器

python 复制代码
def log_decorator(func):
    def wrapper():
        print("函数开始执行")
        func()
        print("函数执行结束")

    return wrapper


@log_decorator
def say_hello():
    print("你好,Python!")


say_hello()

输出:

python 复制代码
函数开始执行
你好,Python!
函数执行结束

这一句:

复制代码
@log_decorator

本质上等价于:

python 复制代码
say_hello = log_decorator(say_hello)

3.2 支持任意参数的装饰器

实际函数往往有参数,因此装饰器通常写成下面这样:

python 复制代码
from functools import wraps

def log_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"正在调用函数:{func.__name__}")

        result = func(*args, **kwargs)

        print(f"函数执行完成,结果:{result}")

        return result

    return wrapper

使用:

python 复制代码
@log_decorator
def add(a, b):
    return a + b


print(add(3, 5))

输出:

python 复制代码
正在调用函数:add
函数执行完成,结果:8
8

@wraps(func) 的作用是保留原函数的名称、注释等元信息,是编写装饰器时的推荐写法。

3.3 FastAPI 中的装饰器

后面会看到,FastAPI 最常见的写法就是装饰器:

python 复制代码
@app.get("/tasks")
async def get_tasks():
    return []

其中:

python 复制代码
@app.get("/tasks")

就是一个装饰器。它的作用是告诉 FastAPI:

复制代码
当客户端发送 GET /tasks 请求时,
请执行 get_tasks 函数。

所以理解装饰器,对学习 FastAPI 非常重要。

四、迭代器与生成器

4.1 什么是迭代

遍历列表时,我们已经使用过 for 循环:

python 复制代码
numbers = [10, 20, 30]

for number in numbers:
    print(number)

这里的列表是可迭代对象。

常见可迭代对象包括:

  • list
  • tuple
  • str
  • set
  • dict
  • 文件对象;
  • 生成器。

4.2 什么是迭代器

迭代器是一个可以不断通过 next() 获取下一个元素的对象。

复制代码
numbers = [10, 20, 30]

iterator = iter(numbers)

print(next(iterator))
print(next(iterator))
print(next(iterator))

输出:

复制代码
10
20
30

当没有更多元素时,再调用 next() 会抛出异常:

复制代码
StopIteration

for 循环本质上也是不断调用 next(),只是 Python 自动帮我们处理了停止异常。

4.3 自定义迭代器

python 复制代码
class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration

        value = self.current
        self.current -= 1

        return value

使用:

python 复制代码
for number in CountDown(3):
    print(number)

输出:

复制代码
3
2
1

4.4 什么是生成器

生成器是更简单的迭代器写法。它使用 yield 返回数据。

python 复制代码
def count_down(start):
    while start > 0:
        yield start
        start -= 1

调用:

python 复制代码
generator = count_down(3)

print(next(generator))
print(next(generator))
print(next(generator))

或者直接使用 for

复制代码
for number in count_down(3):
    print(number)

**生成器最大的优势是节省内存。**例如,如果需要处理一千万条数据:

复制代码
numbers = [i for i in range(10_000_000)]

会一次性生成所有数据并占用内存。而生成器:

python 复制代码
numbers = (i for i in range(10_000_000))

只有真正遍历到某个元素时,才生成那个元素。

4.5 小案例:分批读取数据

python 复制代码
def batch_data(data, batch_size):
    for index in range(0, len(data), batch_size):
        yield data[index:index + batch_size]


users = [
    "用户1", "用户2", "用户3",
    "用户4", "用户5", "用户6",
    "用户7"
]

for batch in batch_data(users, 3):
    print(batch)

输出:

python 复制代码
['用户1', '用户2', '用户3']
['用户4', '用户5', '用户6']
['用户7']

这种"分批处理"的思路,在爬虫、数据库批量写入、文件处理、消息队列消费等场景中很常见。

五、asyncio:Python 的异步编程基础

学习 FastAPI 前,必须先理解 asyncio 的基础。

5.1 同步执行的问题

假设有两个任务:

python 复制代码
import time

def download_file():
    print("开始下载文件")
    time.sleep(3)
    print("文件下载完成")

def send_message():
    print("开始发送消息")
    time.sleep(2)
    print("消息发送完成")

download_file()
send_message()

总耗时约为:

python 复制代码
3 秒 + 2 秒 = 5 秒

因为第一个任务没有结束,第二个任务不能开始。

5.2 异步执行

python 复制代码
import asyncio

async def download_file():
    print("开始下载文件")
    await asyncio.sleep(3)
    print("文件下载完成")

async def send_message():
    print("开始发送消息")
    await asyncio.sleep(2)
    print("消息发送完成")

async def main():
    await asyncio.gather(
        download_file(),
        send_message()
    )

asyncio.run(main())

总耗时约为:

python 复制代码
3 秒

因为两个任务在等待 I/O 时可以交替执行。

5.3 asyncawait

python 复制代码
async def task():
    await asyncio.sleep(1)

含义:

  • async def:定义协程函数;
  • await:等待一个异步操作完成,同时让出执行权;
  • asyncio.run():启动事件循环并执行协程;
  • asyncio.gather():并发等待多个协程完成。

5.4 一个重要误区

异步不是"让所有代码都变快"。如果函数是 CPU 密集型任务,例如:

python 复制代码
def calculate():
    total = 0

    for i in range(100_000_000):
        total += i

    return total

即使写成 async def,也不会自动加速。

异步更适合 I/O 密集型场景,例如:

  • 请求第三方接口;
  • 查询数据库;
  • 读写文件;
  • 等待 Redis;
  • 等待消息队列;
  • 等待网络响应。

在异步函数中,不要使用阻塞式的:

复制代码
time.sleep(3)

而应该使用:

复制代码
await asyncio.sleep(3)

六、FastAPI:从零写一个任务管理后端

FastAPI 是一个现代 Python Web 框架,适合开发:

  • RESTful API;
  • 前后端分离项目;
  • 管理后台接口;
  • 微服务;
  • AI 应用后端;
  • 文件上传服务;
  • 数据分析接口;
  • 模型推理接口。

FastAPI 的几个优点:

  • 基于 Python 类型注解;
  • 自动校验请求参数;
  • 自动生成 Swagger 文档;
  • 原生支持异步;
  • 开发体验友好;
  • 适合构建高性能 API。

6.1 安装 FastAPI

建议先创建虚拟环境:

复制代码
python -m venv .venv

Windows 激活虚拟环境:

复制代码
.venv\Scripts\activate

安装 FastAPI:

复制代码
python -m pip install "fastapi[standard]"

项目目录可以先保持简单:

python 复制代码
python_fastapi_demo/
├── main.py
└── requirements.txt

requirements.txt 内容:

复制代码
fastapi[standard]

6.2 第一个 FastAPI 接口

创建 main.py

python 复制代码
from fastapi import FastAPI

app = FastAPI(
    title="任务管理 API",
    description="用于学习 FastAPI 的简单后端项目",
    version="1.0.0"
)

@app.get("/")
async def read_root():
    return {
        "message": "FastAPI 服务启动成功"
    }

启动服务:

复制代码
fastapi dev main.py

也可以使用传统的 Uvicorn 命令:

复制代码
uvicorn main:app --reload

其中:

复制代码
main     -> main.py 文件
app      -> FastAPI() 创建的对象
--reload -> 修改代码后自动重启服务

启动后访问:

复制代码
http://127.0.0.1:8000/

返回:

python 复制代码
{
  "message": "FastAPI 服务启动成功"
}

FastAPI 还会自动生成接口文档:

复制代码
http://127.0.0.1:8000/docs

这是 Swagger UI 页面,可以直接在浏览器中测试接口。FastAPI 的官方文档也提供了使用 Python 基础镜像构建容器化服务的标准目录与启动方式,后续可以把本篇的小项目直接容器化部署。FastAPI 官方部署文档

6.3 路径参数

路径参数是 URL 路径中的动态部分。

python 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/tasks/{task_id}")
async def get_task(task_id: int):
    return {
        "task_id": task_id,
        "message": "查询任务成功"
    }

访问:

复制代码
http://127.0.0.1:8000/tasks/1

返回:

python 复制代码
{
  "task_id": 1,
  "message": "查询任务成功"
}

这里:

复制代码
task_id: int

不仅是类型提示,也是请求参数校验。如果访问:

复制代码
/tasks/abc

FastAPI 会自动返回参数校验错误,因为 abc 不能转换为整数。

6.4 查询参数

查询参数通常放在 URL 的 ? 后面。

python 复制代码
@app.get("/tasks")
async def get_tasks(
    keyword: str | None = None,
    completed: bool | None = None
):
    return {
        "keyword": keyword,
        "completed": completed
    }

访问:

python 复制代码
http://127.0.0.1:8000/tasks?keyword=FastAPI&completed=false

返回:

python 复制代码
{
  "keyword": "FastAPI",
  "completed": false
}

路径参数和查询参数的区别:

类型 示例
路径参数 /tasks/1
查询参数 /tasks?completed=true

七、FastAPI 小项目:任务管理 API

下面使用内存列表模拟数据库,实现一个简单任务管理系统。

支持功能:

  • 查询全部任务;
  • 根据 ID 查询任务;
  • 新增任务;
  • 修改任务状态;
  • 删除任务。

完整 main.py

python 复制代码
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI(
    title="任务管理 API",
    description="FastAPI 入门实战项目",
    version="1.0.0"
)

tasks = [
    {
        "id": 1,
        "title": "学习 Python 函数",
        "completed": True
    },
    {
        "id": 2,
        "title": "学习 FastAPI",
        "completed": False
    }
]


class TaskCreate(BaseModel):
    title: str = Field(
        min_length=1,
        max_length=100,
        description="任务标题"
    )


class TaskUpdate(BaseModel):
    completed: bool = Field(
        description="任务是否完成"
    )


@app.get("/")
async def read_root():
    return {
        "message": "任务管理 API 服务运行中"
    }


@app.get("/tasks")
async def get_tasks(completed: bool | None = None):
    """查询任务列表。"""

    if completed is None:
        return tasks

    return [
        task for task in tasks
        if task["completed"] == completed
    ]


@app.get("/tasks/{task_id}")
async def get_task(task_id: int):
    """根据任务 ID 查询任务。"""

    for task in tasks:
        if task["id"] == task_id:
            return task

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="任务不存在"
    )


@app.post(
    "/tasks",
    status_code=status.HTTP_201_CREATED
)
async def create_task(task: TaskCreate):
    """新增任务。"""

    new_task = {
        "id": len(tasks) + 1,
        "title": task.title,
        "completed": False
    }

    tasks.append(new_task)

    return new_task


@app.patch("/tasks/{task_id}")
async def update_task(
    task_id: int,
    task_data: TaskUpdate
):
    """修改任务完成状态。"""

    for task in tasks:
        if task["id"] == task_id:
            task["completed"] = task_data.completed
            return task

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="任务不存在"
    )


@app.delete("/tasks/{task_id}")
async def delete_task(task_id: int):
    """删除任务。"""

    for index, task in enumerate(tasks):
        if task["id"] == task_id:
            deleted_task = tasks.pop(index)

            return {
                "message": "任务删除成功",
                "task": deleted_task
            }

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="任务不存在"
    )

7.1 Pydantic 模型的作用

python 复制代码
class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100)

这个类用于接收新增任务时的 JSON 数据。当客户端发送:

复制代码
{
  "title": "学习 asyncio"
}

FastAPI 会自动:

  1. 读取请求 JSON;
  2. 检查是否存在 title
  3. 检查 title 是否为字符串;
  4. 检查长度是否符合要求;
  5. 将结果转换为 TaskCreate 对象。

如果客户端发送:

复制代码
{
  "title": ""
}

FastAPI 会自动返回参数校验错误,不需要手动写大量 if 判断。

7.2 测试新增任务接口

请求地址:

复制代码
POST http://127.0.0.1:8000/tasks

请求体:

复制代码
{
  "title": "学习 FastAPI 请求体"
}

返回:

复制代码
{
  "id": 3,
  "title": "学习 FastAPI 请求体",
  "completed": false
}

7.3 测试修改任务状态

请求地址:

复制代码
PATCH http://127.0.0.1:8000/tasks/2

请求体:

复制代码
{
  "completed": true
}

返回:

复制代码
{
  "id": 2,
  "title": "学习 FastAPI",
  "completed": true
}

7.4 HTTPException 的作用

当任务不存在时:

复制代码
raise HTTPException(
    status_code=404,
    detail="任务不存在"
)

客户端会收到:

复制代码
{
  "detail": "任务不存在"
}

常见状态码:

状态码 含义
200 请求成功
201 创建成功
400 请求参数错误
401 未登录或认证失败
403 没有权限
404 资源不存在
500 服务端异常

八、FastAPI 与 asyncio 的关系

你会发现,FastAPI 接口经常写成:

python 复制代码
@app.get("/tasks")
async def get_tasks():
    return tasks

这里的 async def 表示该接口可以使用异步方式执行。例如,模拟一个需要等待数据库查询的接口:

python 复制代码
import asyncio

@app.get("/slow-tasks")
async def get_slow_tasks():
    await asyncio.sleep(2)

    return {
        "message": "模拟数据库查询完成",
        "data": tasks
    }

这里使用:

复制代码
await asyncio.sleep(2)

表示当前请求等待时,不会一直阻塞整个服务。FastAPI 可以在这段等待时间里处理其他请求。

但要注意:

python 复制代码
import time

@app.get("/bad-example")
async def bad_example():
    time.sleep(2)

    return {"message": "不推荐这样写"}

虽然接口定义成了 async def,但 time.sleep() 仍然是阻塞操作,会影响异步性能。

正确思路:

场景 推荐写法
异步 HTTP 请求 使用 httpx.AsyncClient
异步数据库 使用异步数据库驱动
异步 Redis 使用异步 Redis 客户端
模拟异步等待 await asyncio.sleep()
CPU 密集型计算 考虑线程池、进程池、任务队列

因此可以记住一句话:

async def 本身不会让代码变快;真正决定是否异步的是内部调用的操作是否支持 await


总结

本篇从 Python 容器继续向后学习了 Python 后端开发的重要基础。

  • 函数:封装重复逻辑;
  • 类与对象:组织数据和行为;
  • 装饰器:为函数增加额外能力;
  • 迭代器:按顺序获取数据;
  • 生成器:节省内存地生成大量数据;
  • asyncio:处理 I/O 密集型异步任务;
  • FastAPI:快速构建现代 Web API;
  • Pydantic:自动校验请求参数;
  • HTTPException:规范返回接口异常;
  • Swagger:通过 /docs 自动测试接口。

本篇的任务管理 API 目前使用的是内存列表保存数据,服务重启后数据会丢失。

下一篇可以继续将任务管理 API 升级为:

复制代码
FastAPI
+ SQLAlchemy
+ MySQL / SQLite
+ 用户注册登录
+ JWT 权限认证
+ Docker 容器化部署
相关推荐
智购科技自动售卖机厂家1 小时前
设备一到夏天就频繁跳闸,从启动电流追到压缩机电容~YH
数据结构·人工智能·python·eclipse
科技林总1 小时前
Poetry‌介绍
python
东木月1 小时前
PyCharm 集成 Claude Code 插件:安装、配置与使用指南
python·pycharm·编辑器
泡海椒2 小时前
jquick-pdf 防止 PDF 内容分页断裂:keepTogether 属性妙用
java·开发语言·pdf
jerryinwuhan3 小时前
Python快速入门(纯净版)
python
liferecords3 小时前
第 12 讲 · Python 侧接入:火焰图定位热点与间接使用硬件加速
python·性能分析·鲲鹏·火焰图·ctypes
hhzz3 小时前
【OpenCV 入门到精通 11】机器学习应用:KNN、SVM 与 K-Means 实战
人工智能·python·深度学习·opencv
传奇开心果编程3 小时前
【Rust入门练中学】 第1课:从零开始
开发语言·学习·rust