part1: 在 AI 的帮助下,只花了 5 个小时就入门并上线了我的 Python 短网址应用(01)
part2(本文): 在 AI 的帮助下,只花了 5 个小时就入门并上线了我的 Python 短网址应用(02)
我的目标是用 FastAPI 写一个 URL 短网址应用。要实现这个,第一步就是得搞定数据存储。我需要一个地方来保存原始的长 URL 和我生成的短代码之间的对应关系。
AI 告诉我,虽然可以用简单的文件存储,但对于这种需要快速查找和可能扩展的应用,使用数据库是更标准、更高效的做法。对于初学和开发阶段,SQLite 是个不错的选择,因为它就是一个文件,不需要单独安装数据库服务,Python 也内置了支持。但 AI 也提醒我,实际生产环境可能需要更强大的数据库,比如 PostgreSQL 或 MySQL。
为了更方便地用 Python 操作数据库,AI 推荐我学习 SQLAlchemy。它是一个非常流行的 Python ORM (Object-Relational Mapper)。ORM 的作用就像一个翻译官,让我可以用 Python 的类和对象来操作数据库表和数据,而不需要(或者很少需要)手写 SQL 语句。这对于熟悉面向对象编程的我来说,应该会比较自然。
By author: ORM 技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上。
确保在虚拟环境里:我需要先激活我的 fastapi-project
的 .venv
环境。
source .venv/bin/activate
安装 SQLAlchemy:SQLAlchemy 需要单独安装。因为 FastAPI 是基于异步的,AI 建议我安装支持异步的版本(尽管基础 ORM 用法同步异步差别不大)。同时,由于我们选择了 SQLite,还需要安装对应的数据库驱动 aiosqlite
。
pip install sqlalchemy "aiosqlite"
(记得更新 requirements.txt
:pip freeze > requirements.txt
)
定义数据模型 (models.py
):就像在 Node.js 里用 Mongoose 或 Sequelize 定义 Schema 一样,我要用 SQLAlchemy 定义一个映射到数据库表的 Python 类。我在项目根目录下创建一个新文件 models.py
:
# models.py
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func # 用于获取数据库服务器时间
# 创建一个 Base 类,我们的模型类将继承它
Base = declarative_base()
class URLMap(Base):
__tablename__ = "url_maps" # 指定映射的数据库表名
# 定义表的列 (字段)
id = Column(Integer, primary_key=True, index=True) # 主键,自增,加索引
short_code = Column(String, unique=True, index=True) # 短码,唯一,加索引
long_url = Column(String, index=True) # 原始长 URL,加索引方便查找
# default=func.now() 让数据库在插入时自动设置当前时间
created_at = Column(DateTime(timezone=True), server_default=func.now())
# onupdate=func.now() 让数据库在更新时自动更新时间
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
# 这个 __repr__ 方法是为了方便打印对象时查看信息,可选
def __repr__(self):
return f"<URLMap(short_code='{self.short_code}', long_url='{self.long_url}')>"
创建数据库和表 (database.py
):我需要设置数据库连接,并提供一个创建数据库表的方法。创建一个 database.py
文件:
# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# SQLite 数据库文件将保存在项目根目录下的 shortener.db 文件里
SQLALCHEMY_DATABASE_URL = "sqlite:///./shortener.db"
# 如果用 PostgreSQL: "postgresql://user:password@postgresserver/db"
# 创建 SQLAlchemy 引擎
# connect_args 是 SQLite 特有的,建议加上以提高并发性能
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# 创建一个 SessionLocal 类,每个实例将是一个数据库会话
# autocommit=False 和 autoflush=False 是常用的设置
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 重新导入在 models.py 中定义的 Base
# (也可以直接从 models 导入 Base = models.Base)
Base = declarative_base()
def create_db_and_tables():
# 这会查找所有继承自 Base 的类,并在数据库中创建对应的表
# 如果表已存在,则不会重复创建
Base.metadata.create_all(bind=engine)
# 我们可以在应用启动时调用 create_db_and_tables()
在主应用启动时创建表:修改 main.py
,在应用启动时调用创建表的函数。
# main.py
from fastapi import FastAPI
# ... 其他导入 ...
from database import engine, SessionLocal # 导入数据库设置
import models # 导入模型定义
# 在应用启动时创建数据库表
# 注意:这里需要确保 models.py 中的 Base 被正确关联
# 更稳妥的方式是在 database.py 中导入并绑定 models.Base
# 或者直接在这里调用 models.Base.metadata.create_all(bind=engine)
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# ... 路由定义保持不变 ...
修正:确保 create_db_and_tables
或 models.Base.metadata.create_all
被正确调用。直接在 main.py
调用 models.Base.metadata.create_all(bind=engine)
更直接。
我的小练习: 运行 python main.py
(或者直接运行 uvicorn),观察项目目录下是否生成了 shortener.db
文件。你可以使用 DB Browser for SQLite 等工具打开看看里面是否创建了 url_maps
表。
(感受:SQLAlchemy 的概念比我想象的要多,engine
, sessionmaker
, Base
这些都需要理解。但是用 Python 类来定义表结构确实很直观,有点像定义 TypeScript 接口然后 ORM 自动处理数据库的感觉。create_all
这个函数很方便,不用手动写 CREATE TABLE
语句了。)
在 pip install
命令中,引号(单引号 '
或双引号 "
)的主要作用是防止 shell(命令行解释器,如 Bash 或 Zsh)对包名或版本说明符进行特殊处理。
具体来说:
简单包名(如 sqlalchemy
):
sqlalchemy
,shell 会将其作为一个单独的参数传递给 pip
命令。"sqlalchemy"
)也可以,但通常没有必要。shell 会移除引号,然后将 sqlalchemy
传递给 pip
。包含特殊字符的包说明符(如 "aiosqlite"
或 "requests>=2.20"
或 "uvicorn[standard]"
):
"aiosqlite"
:这个包名本身很简单,不包含特殊字符,所以引号不是必需的。pip install sqlalchemy aiosqlite
和 pip install sqlalchemy "aiosqlite"
的效果是一样的。加上引号可能是个人习惯,或者是在一个包含其他需要引号的包的命令中为了保持一致性。>
, <
, =
, ~
):像 requests>=2.20
这样的写法包含了 >=
。在某些 shell 中,>
有重定向的特殊含义。为了确保 shell 不会尝试进行重定向,而是将整个 requests>=2.20
字符串传递给 pip
,需要加上引号,例如 pip install "requests>=2.20"
。[]
):像 uvicorn[standard]
这样的写法包含了方括号 []
。在 shell 中,方括号通常用于通配符(globbing)。为了防止 shell 尝试查找匹配 uvicorn[standard]
模式的文件名,需要加上引号,例如 pip install "uvicorn[standard]"
。&
, ;
等特殊字符,也需要引号。总结:
-
/_
的简单包名,引号不是必需的。>=
, ==
, <
等) 或 extras ([]
) 的包,强烈建议使用引号,以防止 shell 错误地解释这些特殊字符。pip install sqlalchemy "aiosqlite"
中,给 aiosqlite
加引号是可选的,但没有坏处。定义好了数据库模型,接下来就是实现短网址应用的核心功能:
我们需要在数据库会话 (Session) 中执行这些操作。
创建 crud.py
文件:为了将数据库操作逻辑和 API 路由逻辑分开,我创建一个 crud.py
文件来存放这些函数。
# crud.py
from sqlalchemy.orm import Session
import models
import random
import string
# 生成随机短码的函数 (简单实现)
def generate_short_code(length: int = 6) -> str:
# 使用大小写字母和数字生成随机字符串
characters = string.ascii_letters + string.digits
return ''.join(random.choice(characters) for _ in range(length))
# 根据短码获取 URL 映射记录
def get_url_map_by_short_code(db: Session, short_code: str):
# db.query(models.URLMap) 开始一个查询
# .filter(models.URLMap.short_code == short_code) 添加过滤条件
# .first() 返回第一个匹配的记录,如果没有则返回 None
return db.query(models.URLMap).filter(models.URLMap.short_code == short_code).first()
# 根据长 URL 获取 URL 映射记录 (防止重复创建)
def get_url_map_by_long_url(db: Session, long_url: str):
return db.query(models.URLMap).filter(models.URLMap.long_url == long_url).first()
# 创建新的 URL 映射记录
def create_url_map(db: Session, long_url: str):
# 循环生成短码,直到找到一个数据库里不存在的
while True:
short_code = generate_short_code()
db_url_map = get_url_map_by_short_code(db, short_code)
if not db_url_map:
break # 找到未使用的短码
# 创建模型实例
db_url_map = models.URLMap(short_code=short_code, long_url=long_url)
# 添加到会话
db.add(db_url_map)
# 提交事务到数据库
db.commit()
# 刷新实例,获取数据库生成的值 (比如 id, created_at)
db.refresh(db_url_map)
return db_url_map
理解数据库会话 (Session):AI 解释说,Session
对象是与数据库交互的主要接口。所有的数据库操作(添加、查询、删除)都是通过 Session
进行的。你需要先 db.add()
把对象添加到会话"暂存区",然后 db.commit()
把这些更改一次性写入数据库。如果中间出错,可以 db.rollback()
回滚。
我的小练习: 在 crud.py
中添加一个函数 get_all_url_maps(db: Session, skip: int = 0, limit: int = 100)
,用于获取数据库中所有的 URL 映射记录(支持分页)。(提示:使用 db.query(...).offset(skip).limit(limit).all()
)
(感受:把数据库操作封装在 crud.py
里是好习惯,保持了代码分离。SQLAlchemy 的查询语法 .query().filter().first()
读起来很流畅。生成唯一短码的逻辑需要考虑碰撞,这里的简单循环重试在量小的时候可行,量大时可能需要更优化的策略。db.add
, db.commit
, db.refresh
这套流程需要记住。)
现在有了数据库模型和 CRUD 操作函数,是时候把它们连接到 FastAPI 的 API 端点了。我们需要创建两个主要的端点:
POST /shorten
:接收长 URL,返回短 URL。GET /{short_code}
:接收短代码,重定向到原始长 URL。这里要用到 FastAPI 的一个核心特性:依赖注入 (Dependency Injection)。简单来说,就是让 FastAPI 自动为我们的路径操作函数提供所需的依赖项(比如数据库会话 Session
)。
定义 Pydantic 模型 (schemas.py
):为了清晰定义 API 的输入输出数据结构,并与数据库模型 (ORM model) 分离,通常会创建 Pydantic 模型。我创建一个 schemas.py
文件:
# schemas.py
from pydantic import BaseModel, HttpUrl
from datetime import datetime
from typing import Optional
# 请求体模型:用于 POST /shorten
class URLCreate(BaseModel):
long_url: HttpUrl # Pydantic 自带的 HttpUrl 类型可以做 URL 校验
# 基础响应模型 (用于读取)
class URLMapBase(BaseModel):
short_code: str
long_url: HttpUrl
# 完整的响应模型 (可能包含数据库生成的字段)
class URLMap(URLMapBase):
id: int
created_at: datetime
updated_at: datetime
# 这个配置让 Pydantic 模型可以从 ORM 对象 (数据库记录) 创建
class Config:
orm_mode = True
# 在 Pydantic V2 中,是 from_attributes = True
# from pydantic_settings import BaseSettings
# model_config = SettingsConfigDict(from_attributes=True)
注意:Pydantic V2 中 orm_mode
已改为 from_attributes
。根据你的 Pydantic 版本调整。
创建获取数据库会话的依赖项:在 database.py
中定义一个函数,用于生成数据库会话,并确保在使用后关闭。
# database.py
# ... 其他代码 ...
# 依赖项:获取数据库会话
def get_db():
db = SessionLocal()
try:
yield db # 使用 yield 将会话提供给路径操作函数
finally:
db.close() # 确保在请求结束后关闭会话
修改 main.py
,添加路由并使用依赖注入:
# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
import models, schemas, crud # 导入相关模块
from database import engine, SessionLocal, get_db # 导入 get_db
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 创建短网址的端点
@app.post("/shorten", response_model=schemas.URLMap)
def create_short_url(url_in: schemas.URLCreate, db: Session = Depends(get_db)):
# 检查长 URL 是否已存在
db_url = crud.get_url_map_by_long_url(db, str(url_in.long_url))
if db_url:
return db_url # 如果已存在,直接返回现有的记录
# 创建新的 URL 映射
new_url_map = crud.create_url_map(db=db, long_url=str(url_in.long_url))
return new_url_map
# 重定向端点
@app.get("/{short_code}")
def redirect_to_long_url(short_code: str, db: Session = Depends(get_db)):
db_url = crud.get_url_map_by_short_code(db, short_code)
if db_url is None:
# 如果短码不存在,返回 404 Not Found
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short URL not found")
# 执行重定向
return RedirectResponse(url=str(db_url.long_url), status_code=status.HTTP_301_MOVED_PERMANENTLY)
db: Session = Depends(get_db)
: 这就是依赖注入。FastAPI 看到这个,会自动调用 get_db
函数,获取一个数据库会话 db
,并将其传递给 create_short_url
或 redirect_to_long_url
函数。response_model=schemas.URLMap
: 指定了 /shorten
端点成功响应的数据结构。RedirectResponse
: 用于返回 HTTP 重定向。HTTPException
: 用于返回标准的 HTTP 错误响应。我的小练习: 运行 uvicorn main:app --reload
,使用 /docs
交互式文档测试 /shorten
端点,创建一个短网址。然后直接在浏览器地址栏输入生成的短网址(如 http://127.0.0.1:8000/xxxxxx
),看是否能成功重定向到原始的长 URL。
(感受:依赖注入 Depends(get_db)
的写法很优雅!它把获取和关闭数据库会话的逻辑从我的 API 处理函数中解耦了。使用 Pydantic 模型 (schemas.py
) 来定义 API 的输入 (URLCreate
) 和输出 (URLMap
),使得代码结构非常清晰,FastAPI 还能自动校验和生成文档,这太棒了。RedirectResponse
和 HTTPException
处理重定向和错误也很直接。)
我的 URL 短网址应用现在可以在本地运行了,但最终目标是让别人也能访问。这就需要把它部署到服务器上。
AI 告诉我,部署一个 Python Web 应用(尤其是像 FastAPI 这样的 ASGI 应用)通常涉及几个组件协同工作。以下是一个基础的、手动的部署流程(先不考虑 Docker):
假设我有一台 Linux 服务器(比如 Ubuntu),并且可以通过 SSH 访问。
步骤 1:服务器基础设置
sudo apt update && sudo apt install python3 python3-pip python3-venv
来安装。步骤 2:获取应用代码
sudo apt install git
/var/www/
或用户主目录)克隆我的项目代码:
git clone <你的项目 Git 仓库地址> fastapi-project
cd fastapi-project
步骤 3:设置 Python 虚拟环境和依赖
python3 -m venv .venv
source .venv/bin/activate
requirements.txt
安装所有必需的包:
pip install -r requirements.txt
pip
会安装 FastAPI, Uvicorn, SQLAlchemy, aiosqlite 等。步骤 4:运行 ASGI 服务器 (Gunicorn + Uvicorn)
直接用 uvicorn main:app
运行只适合开发。在生产环境中,通常需要一个更强大的 应用服务器 (Application Server) 来管理 Uvicorn 进程。Gunicorn 是一个常用的选择,它可以管理多个 Uvicorn worker 进程来处理并发请求。
安装 Gunicorn:在虚拟环境里安装 Gunicorn。
pip install gunicorn
(别忘了更新 requirements.txt
)
使用 Gunicorn 运行 Uvicorn worker:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8000
-w 4
: 启动 4 个 worker 进程 (数量通常根据服务器 CPU 核心数调整)。-k uvicorn.workers.UvicornWorker
: 指定 Gunicorn 使用 Uvicorn 来处理 ASGI 应用。main:app
: 和之前一样,指向我的 FastAPI 应用实例。-b 0.0.0.0:8000
: 绑定服务器的 IP 地址和端口。0.0.0.0
表示监听所有网络接口的 8000 端口。现在,我的应用应该可以通过服务器的 IP 地址和 8000 端口访问了 (比如 http://<服务器IP>:8000
)。
步骤 5:使用进程管理器 (Systemd)
直接在终端运行 Gunicorn,如果我关闭了 SSH 连接,或者服务器重启,应用就停了。我们需要一个进程管理器来确保应用在后台持续运行,并且能在出错或服务器重启后自动恢复。
在现代 Linux 系统中,Systemd 是标准的进程管理器。
创建 Systemd service 文件:在 /etc/systemd/system/
目录下创建一个名为 fastapi-app.service
(或其他你喜欢的名字) 的文件,内容类似:
[Unit]
Description=Gunicorn instance to serve FastAPI URL Shortener
After=network.target
[Service]
User=<你的部署用户名> # 替换成你创建的部署用户
Group=www-data # 或你的部署用户所属的组
WorkingDirectory=/path/to/fastapi-project # 替换成你的项目绝对路径
Environment="PATH=/path/to/fastapi-project/.venv/bin" # 确保使用虚拟环境的路径
ExecStart=/path/to/fastapi-project/.venv/bin/gunicorn --workers 4 --worker-class uvicorn.workers.UvicornWorker main:app --bind unix:/tmp/fastapi-app.sock
# 注意:这里改为绑定到 Unix socket 文件,而不是 IP:Port,后面 Nginx 会用到
[Install]
WantedBy=multi-user.target
<你的部署用户名>
和 /path/to/fastapi-project
替换成实际的值。ExecStart
里,我们让 Gunicorn 绑定到一个 Unix socket 文件 (/tmp/fastapi-app.sock
),而不是 TCP 端口。这样做通常性能更好,也更安全,因为只有本地进程(比如 Nginx)能访问它。启用并启动服务:
sudo systemctl enable fastapi-app.service # 设置开机自启
sudo systemctl start fastapi-app.service # 启动服务
sudo systemctl status fastapi-app.service # 查看服务状态
现在 Gunicorn 和我的 FastAPI 应用应该在后台稳定运行了。
步骤 6:设置反向代理 (Nginx)
虽然 Gunicorn 已经在运行了,但直接把它暴露给公网不是最佳实践。通常会在前面放一个 Web 服务器 作为 反向代理,比如 Nginx 或 Apache。
Nginx 的好处:
安装 Nginx:sudo apt install nginx
配置 Nginx:在 Nginx 的配置目录 (通常是 /etc/nginx/sites-available/
) 创建一个新的配置文件,比如 fastapi_app
,内容类似:
server {
listen 80; # 监听 HTTP 80 端口
server_name your_domain.com www.your_domain.com; # 替换成你的域名,或者服务器 IP
location / {
proxy_pass http://unix:/tmp/fastapi-app.sock; # 将所有请求转发给 Gunicorn 的 socket
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 如果有静态文件,可以在这里配置
# location /static {
# alias /path/to/fastapi-project/static;
# }
}
启用配置并重启 Nginx:
sudo ln -s /etc/nginx/sites-available/fastapi_app /etc/nginx/sites-enabled/ # 创建软链接启用配置
sudo nginx -t # 测试 Nginx 配置是否正确
sudo systemctl restart nginx # 重启 Nginx
现在,我应该可以通过我的域名或服务器 IP 地址直接访问我的 FastAPI 应用了 (Nginx 会把请求转发给后台的 Gunicorn/Uvicorn/FastAPI)。
我的小练习: (这个比较复杂,暂时不做) 梳理一遍这个部署流程,理解每个组件(Git, venv, pip, Gunicorn, Uvicorn, Systemd, Nginx)的作用和它们之间的关系。
(感受:部署比我想象的要复杂!涉及好多组件。Gunicorn 管理 Uvicorn 进程,Systemd 管理 Gunicorn 进程,Nginx 做反向代理处理外部请求并转发给 Gunicorn。虽然步骤多,但每个组件职责清晰。这和部署 Node.js 应用(比如用 PM2 管理进程,用 Nginx 反代)的思路有很多相似之处,只是具体工具不同。先理解这个流程,以后再考虑用 Docker 简化它。)
经过这一系列的步骤,我已经成功地:
venv
) 和包管理器 (pip
)。async/await
)。SQLAlchemy
) 和 API 逻辑。虽然我的短网址应用还很简单(比如短码生成策略可以优化、可以增加点击统计等),并且部署流程也可以用 Docker 进一步简化,但达到这个程度,我已经算完成了最初设定的目标!
接下来我可以:
我的 Python + FastAPI 之旅,才刚刚开始!
之前我学习了手动部署的流程,虽然能跑起来,但步骤繁琐,而且每次换环境都要重复配置,感觉很麻烦。AI 告诉我,这正是 Docker 发挥作用的地方!
Docker 可以把我的应用代码、Python 解释器、所有依赖项(就像 requirements.txt
里的那些)以及运行所需的配置,一起打包到一个叫做 镜像 (Image) 的标准单元里。然后,我可以在任何支持 Docker 的机器上,用这个镜像运行一个 容器 (Container),容器就是一个隔离的、包含了应用运行所需一切的环境。这就像是把我的整个开发环境(或者说,一个精简版的运行环境)打包带走。
这和前端开发中,有时会用 Docker 来确保 Node.js 版本一致、或者打包一个 Next.js 应用进行部署的思路很像,都是为了环境一致性和简化部署。
Dockerfile
就是一个文本文件,里面包含了一系列指令,告诉 Docker 如何构建我的应用镜像。
我在我的 fastapi-project
根目录下创建一个名为 Dockerfile
(没有扩展名) 的文件,内容如下:
# Dockerfile
# 1. 选择一个基础镜像
# Python 官方提供了包含 Python 环境的镜像
# 选择一个具体的版本,比如 3.11,并使用 slim 版本以减小体积
FROM python:3.11-slim
# 2. 设置工作目录
# 在容器内创建一个目录,后续命令都在这里执行
WORKDIR /app
# 3. 复制依赖文件
# 将 requirements.txt 复制到容器的工作目录
COPY requirements.txt requirements.txt
# 4. 安装依赖
# 在容器内运行 pip install,安装所有依赖项
# --no-cache-dir 可以减少镜像体积
RUN pip install --no-cache-dir -r requirements.txt
# 5. 复制应用代码
# 将项目目录下的所有文件复制到容器的工作目录 /app
COPY . .
# 6. 暴露端口 (可选,但推荐)
# 告诉 Docker 容器将会监听哪个端口 (和 Uvicorn/Gunicorn 运行的端口一致)
EXPOSE 8000
# 7. 定义容器启动时运行的命令
# 使用 Uvicorn 启动 FastAPI 应用
# 使用 0.0.0.0 监听所有接口,端口 8000
# 注意:这里用 uvicorn 直接启动,适合简单场景或结合其他工具 (如 K8s)
# 如果需要 Gunicorn 管理多进程,CMD 可以是 gunicorn 命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Dockerfile 指令解释:
FROM
: 指定基础镜像。WORKDIR
: 设置容器内的工作目录。COPY
: 将本地文件复制到容器内。RUN
: 在构建镜像过程中执行命令(比如安装依赖)。EXPOSE
: 声明容器运行时监听的端口(信息性,实际端口映射在 docker run
时指定)。CMD
: 指定容器启动时默认执行的命令。fastapi-project
目录下 (包含 Dockerfile
的地方),然后运行:
docker build -t fastapi-shortener .
docker build
: 构建命令。-t fastapi-shortener
: 给镜像打上标签 (tag),方便引用,格式是 repository:tag
(这里省略了 tag,默认为 latest
)。.
: 表示 Dockerfile 所在的当前目录。Dockerfile
里的步骤执行,下载基础镜像、复制文件、安装依赖等。第一次构建可能需要点时间。docker run -d -p 8000:8000 --name shortener-app fastapi-shortener
docker run
: 运行容器命令。-d
: 在后台 (detached mode) 运行容器。-p 8000:8000
: 将宿主机的 8000 端口映射到容器的 8000 端口 (格式是 hostPort:containerPort
)。这样我就可以通过 http://localhost:8000
访问容器里的应用了。--name shortener-app
: 给运行的容器起个名字,方便管理。fastapi-shortener
: 使用哪个镜像来创建容器。http://localhost:8000
或 http://localhost:8000/docs
来访问我的 FastAPI 应用了,只不过这次它运行在 Docker 容器里!docker ps
docker stop shortener-app
docker start shortener-app
docker logs shortener-app
docker rm shortener-app
docker images
docker rmi fastapi-shortener
我的小练习: 成功构建并运行我的短网址应用的 Docker 容器,并通过浏览器访问 /docs
确认它正常工作。
(感受:Dockerfile 的语法挺直观的,有点像写部署脚本。把环境和依赖都打包在一起确实解决了环境不一致的问题。docker build
和 docker run
的流程跟我之前用 Docker 构建 Node.js 应用很像。虽然 Docker 内部还有很多概念(网络、卷等),但基础的打包和运行流程掌握起来不难。这对于部署来说,比之前的手动流程规范和方便多了!)
虽然 FastAPI 主要用于构建 API,但它也能提供静态文件服务。为了让我的短网址应用更完整一点,AI 建议我可以加一个最简单的前端页面,用来输入长 URL 并显示生成的短 URL。
创建静态文件目录:在 fastapi-project
根目录下创建一个名为 static
的文件夹。
创建 index.html
:在 static
文件夹里创建 index.html
文件。
<!-- static/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>URL 缩短器</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
}
input[type='url'] {
width: 80%;
padding: 8px;
margin-right: 5px;
}
button {
padding: 8px 15px;
}
#result {
margin-top: 15px;
font-weight: bold;
}
#error {
margin-top: 15px;
color: red;
}
</style>
</head>
<body>
<h1>URL 缩短器</h1>
<form id="shorten-form">
<input
type="url"
id="long-url"
placeholder="输入要缩短的长网址"
required
/>
<button type="submit">缩短</button>
</form>
<div id="result"></div>
<div id="error"></div>
<script>
const form = document.getElementById('shorten-form');
const longUrlInput = document.getElementById('long-url');
const resultDiv = document.getElementById('result');
const errorDiv = document.getElementById('error');
form.addEventListener('submit', async (event) => {
event.preventDefault(); // 阻止表单默认提交行为
resultDiv.textContent = ''; // 清空上次结果
errorDiv.textContent = ''; // 清空上次错误
const longUrl = longUrlInput.value;
try {
// 调用后端的 /shorten API
const response = await fetch('/shorten', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ long_url: longUrl }),
});
if (!response.ok) {
// 如果 API 返回错误状态码
const errorData = await response.json();
throw new Error(
errorData.detail || `HTTP error! status: ${response.status}`
);
}
const data = await response.json();
const shortUrl = `${window.location.origin}/${data.short_code}`;
resultDiv.innerHTML = `短网址: <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
longUrlInput.value = ''; // 清空输入框
} catch (error) {
console.error('Error:', error);
errorDiv.textContent = `出错了: ${error.message}`;
}
});
</script>
</body>
</html>
这个 HTML 包含一个表单和一个简单的 JavaScript 片段,用于:
fetch
API 向后端的 /shorten
发送 POST 请求。修改 main.py
以提供静态文件服务:我需要告诉 FastAPI 如何找到并提供 static
目录下的文件。
# main.py
from fastapi import FastAPI, Depends, HTTPException, status, Request
from fastapi.responses import RedirectResponse, HTMLResponse # 导入 HTMLResponse
from fastapi.staticfiles import StaticFiles # 导入 StaticFiles
from fastapi.templating import Jinja2Templates # 如果需要模板引擎
from sqlalchemy.orm import Session
import models, schemas, crud
from database import engine, SessionLocal, get_db
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 挂载静态文件目录
# "/static" 是 URL 路径, directory="static" 是本地目录名
app.mount("/static", StaticFiles(directory="static"), name="static")
# (可选) 如果你想让 index.html 在根路径 `/` 提供服务
# 可以创建一个模板实例
# templates = Jinja2Templates(directory="static")
# @app.get("/", response_class=HTMLResponse)
# async def read_index(request: Request):
# return templates.TemplateResponse("index.html", {"request": request})
# 或者更简单地,为根路径创建一个路由直接返回 index.html
@app.get("/", response_class=HTMLResponse)
async def read_index():
with open("static/index.html") as f:
return HTMLResponse(content=f.read(), status_code=200)
# --- 其他 API 路由保持不变 ---
@app.post("/shorten", response_model=schemas.URLMap)
def create_short_url(url_in: schemas.URLCreate, db: Session = Depends(get_db)):
# ... (省略之前的代码)
db_url = crud.get_url_map_by_long_url(db, str(url_in.long_url))
if db_url:
return db_url
new_url_map = crud.create_url_map(db=db, long_url=str(url_in.long_url))
return new_url_map
@app.get("/{short_code}")
def redirect_to_long_url(short_code: str, db: Session = Depends(get_db)):
# ... (省略之前的代码)
db_url = crud.get_url_map_by_short_code(db, short_code)
if db_url is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Short URL not found")
return RedirectResponse(url=str(db_url.long_url), status_code=status.HTTP_301_MOVED_PERMANENTLY)
# ... (省略 __main__ 部分) ...
app.mount("/static", StaticFiles(directory="static"), name="static")
: 这行代码告诉 FastAPI,所有以 /static
开头的 URL 请求,都应该去本地的 static
目录查找文件。/
的 GET 路由,它直接读取并返回 static/index.html
的内容。测试:运行 uvicorn main:app --reload
。现在访问 http://127.0.0.1:8000/
应该就能看到我创建的 URL 缩短器页面了。尝试输入一个 URL 并点击"缩短",看看是否能正常工作。
我的小练习: 稍微美化一下 index.html
的样式,或者在显示结果时增加一个"复制"按钮。
(感受:用 FastAPI 提供静态文件服务比我想象的简单,app.mount
就搞定了。前端的 JS 代码和在 Node.js 应用里写前端交互逻辑基本一样,都是用 fetch
调用 API。这种方式适合给 API 配一个简单的管理界面或测试页面。当然,对于复杂的单页应用 (SPA),我还是会选择用 React/Vue/Next.js 分开构建,然后让 FastAPI 纯粹提供 API。)
我的应用现在能在本地跑,也能打包进 Docker,甚至还有个简单的前端界面。但 AI 提醒我,要把它真正放到生产环境稳定运行,还有很多事情要做。这就像我完成了一个前端组件,但还需要考虑测试、性能、安全性、部署流程等才能合并到主分支一样。
AI 帮我梳理了一个生产准备清单:
配置管理:
SQLALCHEMY_DATABASE_URL
) 现在是硬编码在 database.py
里的,这在生产环境肯定不行,比如生产数据库的地址和密码不能直接写在代码里。.env
文件和 dotenv
库一样,Python 也有类似机制。推荐使用 环境变量 来传递配置。Pydantic 的 Settings Management 功能可以很方便地从环境变量或 .env
文件加载配置。HTTPS/SSL:
日志记录 (Logging):
logging
模块。我需要配置它来记录应用的运行信息、API 请求、错误等,并将日志输出到文件或集中的日志系统。logging
模块,并集成到 FastAPI 中记录请求和异常。错误监控与告警:
数据库迁移 (Migrations):
URLMap
加个 clicks
计数列),直接修改 models.py
并不能自动更新现有的数据库表结构。更健壮的测试:
pytest
和 FastAPI 的 TestClient
来编写测试用例。安全性加固:
npm audit
)。性能与扩展性:
index=True
)。CI/CD (持续集成/持续部署):
(感受:哇,生产环境要考虑的事情真不少!配置、安全、日志、监控、测试、部署自动化... 这清单让我意识到写代码只是第一步。不过很多概念(像环境变量、HTTPS、日志、测试、CI/CD)和我在前端/Node.js 开发中接触到的思路是相通的,只是具体工具和实现方式不同。感觉 Python 生态在这些方面应该也有成熟的解决方案。)
既然我选择了 FastAPI,AI 也建议我了解一下 Python 世界里另外两个非常流行的 Web 框架:Django 和 Flask,这样能更好地理解 FastAPI 的定位和优缺点。
特性/方面 | Flask | Django | FastAPI |
---|---|---|---|
类型 | 微框架 (Microframework) | 全栈框架 ("Batteries-included") | 微框架 (API 重点) / 异步框架 |
核心理念 | 保持核心简单,高度可扩展,灵活 | 约定优于配置,快速开发,功能全面 | 高性能 (异步),类型提示,自动文档,开发者体验 |
自带功能 | 路由、请求响应处理、模板 (Jinja2) | ORM、Admin 后台、认证、表单、模板、缓存等 | 路由、异步、数据校验 (Pydantic)、依赖注入、自动文档 |
异步支持 | 原生不支持 (需扩展或 ASGI 适配器) | 近期版本开始加强异步支持 (ORM, Views 等) | 核心特性 (基于 Starlette 和 asyncio) |
数据校验 | 依赖扩展 (如 Flask-WTF, Marshmallow) | 自带表单和模型校验 | 核心特性 (深度集成 Pydantic) |
数据库 ORM | 依赖扩展 (如 Flask-SQLAlchemy) | 自带强大 ORM | 依赖扩展 (常用 SQLAlchemy) |
模板引擎 | Jinja2 (默认) | 自带模板引擎 (DTL),也支持 Jinja2 | 不直接绑定,可集成 Jinja2 等 (但更侧重 API) |
Admin 后台 | 无 (需扩展) | 自带,非常强大 | 无 (可自己构建或用第三方库) |
学习曲线 | 较低 (核心简单) | 较高 (功能多,概念多) | 中等 (需要理解类型提示、异步、依赖注入) |
灵活性 | 非常高 | 较低 (更遵循框架约定) | 较高 (比 Django 灵活) |
适合场景 | 简单应用、API、微服务、需要高度定制的项目 | 大型复杂 Web 应用、内容管理系统、需要快速原型开发 | API、微服务、需要高性能/高并发的应用、类型安全是重点 |
类比 Node.js | 类似 Express.js (轻量、灵活) | 类似 NestJS (框架感强,功能全) 或 Ruby on Rails | 现代化、专注于 API、性能好,可能没有特别直接的类比 |
为什么 FastAPI 适合我的 URL 短网址应用?
/shorten
, /{short_code}
重定向),FastAPI 天生就是为此设计的。(感受:这个对比很清晰。Flask 看起来更自由,适合从零搭起;Django 功能强大,适合快速构建大型应用但可能有点"重";FastAPI 则是在性能、现代特性和开发效率之间找到了一个很好的平衡点,特别适合构建 API。看来我选择 FastAPI 作为入门和完成这个项目的框架是明智的。)
这次 AI 辅助的 Python 学习旅程真是收获满满!从最初只会前端的我,到现在能够:
虽然我的 URL 短网址应用还很简单,但这个过程让我对 Python 后端开发有了全面的认识和实践经验。我明白了 Python 不仅仅是脚本语言,它在 Web 开发、数据处理等方面同样强大且生态完善。
接下来,我会继续完善我的短网址应用,实践生产环境清单里的各项要求,特别是配置管理、日志、测试和数据库迁移。然后,我可能会尝试用 Docker Compose 把应用和可能的数据库(比如换成 PostgreSQL)一起编排部署。
这段学习经历也让我体会到,不同语言和技术栈之间,很多核心思想是相通的(比如模块化、依赖管理、异步处理、MVC/MVVM 模式、部署流程等)。拥有前端背景,学习 Python 后端感觉顺畅了不少。
感谢 AI 的一路指导!Python 的世界,我来了!
本地跑起来很酷,Docker 打包也搞定了,但我的最终目标是让这个短网址 Demo 能在网上被访问到。手动部署到 VPS 流程我大致了解了,但感觉还是有点麻烦,而且还得花钱买服务器。
我就问 AI,有没有免费的、适合部署这种简单 Demo 的平台?最好能让我省点事。
AI 给我分析了几个流行的平台,并结合我目前用 SQLite 的情况给出了建议:
Vercel: 我比较熟悉,前端项目经常用。理论上它支持 Python Serverless Functions,可以跑 FastAPI。但 关键问题 在于它的函数环境是无状态且文件系统短暂的。这意味着我的 shortener.db
SQLite 文件无法持久保存,每次请求都可能面对一个丢失数据的环境。结论:不改数据库的话,Vercel 不行。 要用 Vercel,我必须换成它支持的云数据库(比如 Vercel Postgres 或 Neon),并把连接信息配置成环境变量。
PythonAnywhere: 这个平台 AI 重点推荐给我,因为它专门为 Python 设计,而且它的免费套餐提供持久化文件系统!这意味着我可以直接部署我现有的、使用 SQLite 的应用,无需修改数据库代码。这对于快速验证 Demo 来说,简直是完美匹配。缺点是免费版有限制,部署流程可能和 Vercel 不太一样。
Render: 它提供免费的 Web Service (跑 FastAPI) 和免费的 PostgreSQL 数据库。部署流程可以通过连接 GitHub 实现自动化,比较现代。缺点是我必须把数据库从 SQLite 换成 PostgreSQL,需要修改代码并学习新的数据库配置。免费实例在不活动时会休眠。
Fly.io: 也是一个不错的选择,提供免费的小型 Postgres 集群,并且对 Docker 支持很好(我正好写了 Dockerfile)。缺点同样是必须换数据库,且主要通过命令行操作,学习曲线可能比 Render 稍高一点点。
Railway: 和 Render 类似,提供免费的使用额度可以跑应用和数据库 (如 PostgreSQL),通过 Git 部署也很方便。缺点还是必须换数据库,并且要注意免费额度。
我的选择思考:
考虑到我只是想快速部署这个 Demo,而且暂时不想花时间去改数据库和学习 PostgreSQL,我决定先研究一下 PythonAnywhere 的部署方法。等以后应用复杂了,或者想学习云数据库了,再考虑 Render 或 Fly.io。
说明:最后我没有部署在 PythonAnywhere 上!!!!!
清晰了!我的下一个具体的行动步骤是:
fastapi-project
代码。main:app
)。shortener.db
能够被应用正确读写。*.pythonanywhere.com
域名,并测试应用。搞定部署后,我的这个学习项目就算真正落地了!
# 语法 1: 使用循环和 random.choice
def generate_short_code_v1(length: int = 6) -> str:
characters = string.ascii_letters + string.digits
# 每次循环随机选择一个字符,然后用 join 连接起来
return ''.join(random.choice(characters) for _ in range(length))
# 语法 2: 使用 random.choices
def generate_short_code_v2(length: int = 6) -> str:
characters = string.ascii_letters + string.digits
# 直接从 characters 中随机选择 length 个字符 (允许重复),返回一个列表
# 然后用 join 连接起来
return ''.join(random.choices(characters, k=length))
这两种语法都能达到生成指定长度随机字符串的目的,主要区别在于实现方式和表达的简洁性:
random.choice(sequence)
: 这个函数从给定的序列(字符串、列表等)中随机选择一个元素并返回。语法 1 使用了一个生成器表达式 (random.choice(characters) for _ in range(length))
,它会循环 length
次,每次调用 random.choice
得到一个随机字符,最后 ''.join()
将这些逐个生成的字符连接成一个字符串。random.choices(population, k=n)
: 这个函数(注意是 choices
,复数)直接从给定的 population
(序列)中一次性随机选择 k
个元素(默认允许重复选择),并返回一个包含这 k
个元素的列表。语法 2 调用它得到一个包含 length
个随机字符的列表,然后 ''.join()
将这个列表连接成字符串。哪个更好?
random.choices
) 通常被认为更简洁、更 Pythonic,因为它直接表达了"随机选择 k 个元素"的意图,而不是通过循环逐个构建。random.choices
内部实现可能比显式 Python 循环调用 random.choice
更优化一些,但对于短字符串(比如短码),性能差异通常可以忽略不计。类比 Node.js: 这有点像你需要生成一个包含 6 个随机数字的数组:
for
循环,每次循环 Math.random()
并 push
到数组。generateRandomItems(sourceArray, count)
函数,它直接返回所需数量的随机项组成的数组。结论:两者功能相同,但使用 random.choices
是更推荐、更简洁的方式。
__init__
方法的 self
参数必须传且必须在第一个位置吗?class Shortener:
# 这个 self 是必须的吗?必须叫 self 吗?必须放第一位吗?
def __init__(self):
self.db_path = "shortener.db"
self.engine = create_engine(f"sqlite:///{self.db_path}")
self.session = sessionmaker(bind=self.engine)()
是的,对于实例方法 (instance methods)(包括特殊方法如 __init__
),情况如下:
my_shortener = Shortener(); my_shortener.some_method()
),Python 解释器会自动将这个对象(my_shortener
)作为第一个参数传递给方法。因此,方法的定义必须包含一个参数来接收这个实例引用。self
: 这个参数的名字按约定俗成应该是 self
。虽然你可以用其他名字(比如 this
,如果你非常想念 JS 的话),但这强烈不推荐,会违反 Python 社区的广泛约定,让其他 Python 开发者(以及未来的你)阅读代码时感到困惑。类比 Node.js/JavaScript:
__init__
方法非常类似于 JS 类中的 constructor()
方法,都是在创建类的新实例时自动调用的初始化方法。self
参数扮演的角色完全等同于 JS 中的 this
关键字。JS 中的 this
是隐式可用的,你不需要在方法参数列表里声明它。而在 Python 中,你需要显式地在实例方法的参数列表里声明第一个参数(通常命名为 self
)来接收这个实例引用。当你需要访问实例的属性或调用实例的其他方法时,都需要通过 self.attribute
或 self.other_method()
来进行,就像在 JS 中使用 this.property
或 this.method()
一样。总结: 在 Python 类中定义实例方法(如 __init__
, greet
等)时,第一个参数必须存在,用来接收实例本身,并且强烈建议将其命名为 self
。
依赖注入 (Dependency Injection, DI) 是一种设计模式,其核心思想是将一个对象(或函数)所依赖的其他对象(它的"依赖项")从外部传递("注入")给它,而不是让它自己在内部创建这些依赖项。
为什么需要 DI?(好处)
类比 Node.js/前端:
@Injectable()
, @Inject()
) 来实现。require('./db')
或 import config from './config'
。FastAPI 如何实现 DI?
FastAPI 的 DI 系统非常优雅且易于使用,主要通过 Depends
函数实现:
依赖项是一个可调用对象: 一个"依赖项"可以是任何可调用对象 (callable),最常见的是一个函数(比如我们写的 get_db()
函数),也可以是一个类。
在路径操作函数中声明依赖: 在你的 API 路由处理函数(路径操作函数)的参数中,使用 Depends()
来声明依赖。
from fastapi import Depends, FastAPI
from sqlalchemy.orm import Session
from .database import get_db # 假设 get_db 是获取数据库会话的函数
app = FastAPI()
@app.get("/items/")
# db: Session = Depends(get_db) 就是声明依赖
# FastAPI 会调用 get_db(),并将返回值赋给 db 参数
async def read_items(db: Session = Depends(get_db)):
# 在函数体内可以直接使用注入的 db 会话
items = db.query(Item).all()
return items
FastAPI 自动处理: 当请求到达这个路径时,FastAPI 会:
Depends(get_db)
。get_db()
函数。get_db()
函数通常使用 yield
来提供依赖项(比如数据库会话 db
)。yield
出来的值(db
会话)注入到 read_items
函数的 db
参数中。read_items
函数的逻辑。read_items
函数执行完毕(无论成功还是出错),FastAPI 会继续执行 get_db()
函数中 yield
之后的代码(通常是 finally
块里的 db.close()
),确保资源被正确清理。FastAPI DI 的强大之处:
Depends
非常直观。在 Python 中,模块和包是组织代码的基本方式:
模块 (Module):
.py
文件。文件名就是模块名(不含 .py
后缀)。import module_name
或 from module_name import name
来导入和使用模块中的内容。.js
文件模块。包 (Package):
__init__.py
文件(即使它是空的),Python 才会将其识别为一个包。import package_name
),或者导入包中的特定模块 (import package_name.module_name
或 from package_name import module_name
),或者导入模块中的具体内容 (from package_name.module_name import name
)。index.js
或在 package.json
中指定了 main
入口的目录模块。npm
安装和管理的单元,由 package.json
描述,可以包含代码、文档、测试等,并发布到 npm registry。Python 的"包"最初更侧重于代码的组织结构,但现在通过 PyPI (Python Package Index) 和打包工具 (如 setuptools, Poetry),也可以实现类似 Node.js 包的发布和分发。管理方式:
__init__.py
文件来组织包和子包。import
语句来访问包和模块中的代码。Python 会根据其 模块搜索路径 (Python Path) 来查找需要导入的包和模块。这个路径包括:
PYTHONPATH
中指定的目录。pip
(安装/卸载) + venv
(虚拟环境) + requirements.txt
(记录依赖快照)。npm
/yarn
的体验。总结: Python 的模块是单个文件,包是包含 __init__.py
的目录,用于组织模块。通过 import
语句和 Python Path 来管理代码间的引用。第三方包通过 PyPI 查找,并推荐使用 Poetry 或 Pipenv 进行安装和管理。
这里需要区分两个概念:内置函数/类型 (Built-ins) 和 标准库模块 (Standard Library Modules)。
import
即可直接使用。它们是 Python 语言核心的一部分。print()
, len()
, type()
, id()
, input()
, range()
, open()
, abs()
, max()
, min()
, sum()
, sorted()
, reversed()
, isinstance()
, issubclass()
, dir()
, help()
等。int
, float
, str
, bool
, list
, dict
, tuple
, set
, bytes
, NoneType
(对应 None
) 等。Exception
, ValueError
, TypeError
, IndexError
, KeyError
, FileNotFoundError
等。Math
, JSON
, Date
)、全局函数(parseInt
, parseFloat
, isNaN
)、原始类型(string
, number
, boolean
, null
, undefined
, symbol
, bigint
)和内置对象(Array
, Object
, Function
, Promise
, Error
等)。import
语句来加载和使用它们。标准库非常丰富,是 Python 强大的原因之一。os
: 提供了访问操作系统功能的接口,如文件路径操作 (os.path
类似 Node.js 的 path
)、环境变量 (os.environ
)、文件系统操作 (os.listdir
, os.remove
) 等。(类似 Node.js 的 os
, fs
, path
模块的部分功能)sys
: 访问与 Python 解释器紧密相关的变量和函数,如命令行参数 (sys.argv
)、退出解释器 (sys.exit()
)、模块搜索路径 (sys.path
) 等。json
: 用于编码和解码 JSON 数据。(类似 JS 的全局 JSON
对象)datetime
: 处理日期和时间的类 (date
, time
, datetime
, timedelta
)。(功能比 JS 的 Date
强大,更像 Moment.js 或 date-fns)math
: 提供基本的数学函数 (math.sqrt
, math.sin
, math.log
等)。(类似 JS 的 Math
对象)random
: 生成伪随机数、进行随机选择等。re
: 正则表达式操作。(类似 JS 的 RegExp
对象和字符串的正则方法)collections
: 提供了一些有用的数据结构补充,如 defaultdict
(带默认值的字典), Counter
(计数器), deque
(双端队列), namedtuple
(带字段名的元组)。typing
: 提供对类型提示的支持(我们在 FastAPI 中大量使用)。asyncio
: Python 的异步编程框架(async/await
的基础)。http.client
, urllib.request
, urllib.parse
: 用于进行 HTTP 请求和 URL 处理的基础库(但通常大家更喜欢用第三方库 requests
)。logging
: 用于记录日志的标准库。subprocess
: 用于创建和管理子进程。shutil
: 提供高级文件操作(如复制、移动、删除文件和目录)。pathlib
: 面向对象的文件系统路径操作(比 os.path
更现代、更易用)。csv
: 读写 CSV 文件。sqlite3
: 直接支持 SQLite 数据库。venv
: 创建虚拟环境的工具。unittest
, pytest
(虽然 pytest
是第三方,但非常常用): 用于编写和运行测试。总结: Python 有很多无需导入即可使用的内置函数和类型,构成了语言的核心。此外,还有一个极其丰富的标准库,包含了各种模块,你需要 import
它们来使用其功能,涵盖了从文件操作、网络通信到日期时间处理等方方面面。熟悉常用的标准库模块对于高效 Python 开发非常重要。