python
环境管理
系统层(Ubuntu 自带 Python,别动)
↓
pyenv → 管理多版本 Python(如 3.8, 3.10, 3.11)
↓
每个项目/模型/框架 → 用 venv 或 poetry 隔离环境
↓
pip / pipx / conda → 安装你要的工具和包
太好了,如果你这台机器是专门用来学习 AI / 深度学习 / 机器学习,那你对 Python 环境的管理要兼顾 稳定性、灵活性、隔离性和性能。以下是为你的使用场景定制的建议:
✅ 一张图理解整体思路:
系统层(Ubuntu 自带 Python,别动)
↓
pyenv → 管理多版本 Python(如 3.8, 3.10, 3.11)
↓
每个项目/模型/框架 → 用 venv 或 poetry 隔离环境
↓
pip / pipx / conda → 安装你要的工具和包
✅ Step 1: 安装 pyenv
来管理多个 Python 版本
AI 项目常用的 Python 版本:3.8 / 3.10 / 3.11。建议都装上。
# 安装依赖
sudo apt update && sudo apt install -y make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
# 安装 pyenv
curl https://pyenv.run | bash
# 加入到 shell 配置文件中
echo -e '\n# pyenv config\nexport PATH="$HOME/.pyenv/bin:$PATH"\neval "$(pyenv init --path)"\neval "$(pyenv virtualenv-init -)"' >> ~/.zshrc
source ~/.zshrc
装几个常用 Python 版本
pyenv install 3.8.18
pyenv install 3.10.14
pyenv install 3.11.8
pyenv global 3.11.8
✅ Step 2: 为不同 AI 项目创建虚拟环境
你可以选择 venv、pyenv-virtualenv 或 poetry。
✔ 选项 A:用 pyenv-virtualenv(适合你已有 pyenv)
pyenv virtualenv 3.10.14 ai-torch-env
pyenv activate ai-torch-env
pip install torch torchvision
✔ 选项 B:用 poetry(推荐,有依赖锁和打包)
curl -sSL https://install.python-poetry.org | python3 -
poetry new ai-project
cd ai-project
poetry env use 3.11
poetry add numpy torch scikit-learn pandas jupyter
✔ 选项 C:传统 venv
虚拟环境
python3.10 -m venv ~/venvs/ai-env
source ~/venvs/ai-env/bin/activate
pip install transformers pandas matplotlib
✅ Step 3: 建议目录结构
~/AI-Workspace/
├── projects/
│ ├── stable-diffusion/
│ ├── llama-finetune/
│ └── whisper-transcribe/
├── venvs/
│ ├── torch-env/
│ └── llama-env/
├── datasets/
│ └── imagenet/
├── notebooks/
│ └── experimentation.ipynb
✅ 常用开发习惯推荐
- 每个项目一个虚拟环境(用
poetry
或pyenv-virtualenv
管理) - 用
pipx
安装全局 CLI 工具 - 用
requirements.txt
或pyproject.toml
管理依赖 - 不用
sudo pip
安装任何包 nvidia-smi
查看显卡状态,别让显存被占爆
vscode项目管理
pyenv
powershell管理员下安装pyenv-win
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"
pyenv install --list #查看能安装什么版本的python
pyenv install 3.9.7 #安装3.9.7版本的python
但是pyenv在shell里下载好慢,不知道为什么换源也不行,所以我是先用install
看一下从哪里下载,然后直接在浏览器里下载这个,最后拖入到C:\Users\username\.pyenv\pyenv-win\install_cache
文件夹里
这样再用install指令就可以直接安装了
pyenv常用指令
安装和卸载 Python:
- pyenv install -l 或 pyenv install --list
列出所有可用的 Python 版本,包括 CPython、Anaconda、Jython 等。这是你安装新版本前的第一步。
- pyenv install <version>
安装指定版本的 Python。例如:pyenv install 3.9.7。
- pyenv uninstall <version>
卸载指定版本的 Python。例如:pyenv uninstall 3.8.6。
管理和切换 Python 版本:
- pyenv versions
列出所有已安装的 Python 版本,当前正在使用的版本前会有一个星号 *。
- pyenv global <version>
设置全局 Python 版本。这个版本将作为你的默认版本,除非你为特定项目设置了本地版本。例如:pyenv global 3.9.7。
- pyenv local <version>
在当前目录及其子目录中设置本地 Python 版本。这个命令会在当前目录下创建一个 .python-version 文件。当你进入这个目录时,pyenv 会自动切换到指定的版本。例如:pyenv local 3.10.4。
- pyenv local --unset
取消当前目录的本地版本设置,恢复到全局版本。
其他实用指令:
- pyenv rehash
这个命令很少需要手动运行,因为 pyenv-win 会自动执行。它的作用是为新安装的 Python 可执行文件(如 python.exe、pip.exe)创建“垫片文件”(shims),以便 pyenv 能够正确地在 PATH 中找到它们。
- pyenv update
更新 pyenv-win 本身。
vscode
虚拟环境
用pyenv安装完之后用VScode选择解释器就能看到对应的解释器了
但是还要继续安装虚拟环境
pyenv local 3.9.7 #在你的项目目录下创建一个 .python-version 文件,pyenv-win 会自动读取它
python -m venv venv #使用常规的 venv 命令来创建虚拟环境。pyenv-win 会确保它使用的是你刚才指定的本地版本。
执行完上面两个指令后在解释器选择那一栏就可以看到刚刚创建的虚拟环境了,选择就行
工作区
接下来再创建一个工作区,在工作区禁用安装插件不影响总的环境
然后使用Project Manager
插件把这个工作区添加到项目管理就可以了
换源
这byd网太差了,用国外的网的话慢的一笔,所以要换源
在Windows上使用如下指令换成清华源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
使用pip config list
查看配置信息
代理
在powershell里设置临时代理
$env:HTTP_PROXY = "http://127.0.0.1:7890"
$env:HTTPS_PROXY = "http://127.0.0.1:7890"
Linux环境管理
语法
for
for letter in 'Python': # 第一个实例
print("当前字母: %s" % letter)
fruits = ['banana', 'apple', 'mango']
for fruit in fruits: # 第二个实例
print ('当前水果: %s'% fruit)
#通过序列索引迭代
fruits = ['banana', 'apple', 'mango']
for index in range(len(fruits)):
print ('当前水果 : %s' % fruits[index])
pass
Python pass 是空语句,是为了保持程序结构的完整性。
pass 不做任何事情,一般用做占位语句。
#定义空函数/类
def empty_function():
pass # 暂时不实现
class EmptyClass:
pass # 暂时不定义属性和方法
字符串运算符
'''r/R : 原始字符串 - 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母"r"(可以大小写)以外,与普通字符串有着几乎完全相同的语法。'''
>>>print r'\n'
\n
>>> print R'\n'
\n
格式化字符串
print "My name is %s and weight is %d kg!" % ('Zara', 21)
#Output:My name is Zara and weight is 21 kg!
unicode字符串
>>> u'Hello World !'
u'Hello World !'
列表
# 1. 创建列表
empty_list = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, True]
nested = [[1, 2], [3, 4]]
# 2. 访问元素
print(numbers[0]) # 正向索引: 1
print(numbers[-1]) # 负索引: 5
print(numbers[1:3]) # 切片: [2, 3]
print(numbers[::2]) # 步长: [1, 3, 5]
# 3. 修改列表
numbers[0] = 10 # 修改元素
numbers[1:3] = [20, 30] # 修改切片
numbers.append(6) # 末尾添加
numbers.insert(0, 0) # 指定位置插入
numbers.extend([7, 8]) # 扩展列表
# 4. 删除元素
del numbers[0] # 删除指定位置
numbers.remove(30) # 删除首个匹配值
popped = numbers.pop() # 删除并返回末尾元素
numbers.clear() # 清空列表
# 5. 查找与统计
print(20 in numbers) # 成员检查
print(numbers.index(20)) # 查找索引
print(numbers.count(20)) # 计数
# 6. 排序与反转
numbers.sort() # 升序排序
numbers.sort(reverse=True) # 降序
numbers.reverse() # 反转列表
sorted_num = sorted(numbers) # 返回新列表
# 7. 列表生成式
squares = [x**2 for x in range(5)]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
# 8. 复制列表
shallow_copy = numbers.copy()
deep_copy = [x[:] for x in nested] # 嵌套列表深拷贝
# 9. 其他操作
print(len(numbers)) # 长度
print(min(numbers)) # 最小值
print(max(numbers)) # 最大值
print(sum(numbers)) # 求和
# 10. 迭代列表
for item in numbers:
print(item)
for index, value in enumerate(numbers):
print(f"索引{index}: 值{value}")
# 11. 列表与字符串互转
chars = list("hello") # 字符串转列表
word = ''.join(chars) # 列表转字符串
# 12. 过滤与映射
filtered = list(filter(lambda x: x > 2, numbers))
mapped = list(map(lambda x: x * 2, numbers))
# 13. 堆栈与队列
stack = []
stack.append(1) # 入栈
stack.pop() # 出栈
from collections import deque
queue = deque([1, 2, 3])
queue.append(4) # 入队
queue.popleft() # 出队
# 14. 列表解包
first, *rest = [1, 2, 3, 4]
print(first) # 1
print(rest) # [2, 3, 4]
# 15. 性能优化技巧
# 使用生成器代替大列表
big_data = (x for x in range(1000000))
元组
# 1. 创建元组
t1 = (1, 2, 3)
t2 = tuple([4, 5, 6])
t3 = (7,) # 单元素元组必须有逗号
# 2. 访问元素
print(t1[0]) # 索引访问: 1
print(t1[-1]) # 负索引: 3
print(t1[1:3]) # 切片: (2, 3)
# 3. 元组解包
x, y, z = t1 # x=1, y=2, z=3
first, *rest = t2 # first=4, rest=[5,6]
# 4. 不可变特性
# t1[0] = 10 # 报错,元组不可修改
# 5. 常用方法
print(t1.count(2)) # 计数: 1
print(t1.index(3)) # 查找索引: 2
print(len(t1)) # 长度: 3
# 6. 元组拼接
t4 = t1 + t2 # (1,2,3,4,5,6)
# 7. 遍历元组
for item in t1:
print(item)
# 8. 元组与列表转换
lst = list(t1)
t = tuple(lst)
字典
# 1. 创建字典
d1 = {'a': 1, 'b': 2}
d2 = dict(a=1, b=2)
d3 = dict([('a',1),('b',2)])
# 2. 访问元素
print(d1['a']) # 键访问: 1
print(d1.get('c', 0)) # 安全访问: 0
# 3. 修改字典
d1['a'] = 10 # 修改值
d1['c'] = 3 # 添加键值对
d1.update({'d':4}) # 批量更新
# 4. 删除元素
del d1['a'] # 删除键
val = d1.pop('b') # 删除并返回值
# 5. 常用方法
print(d1.keys()) # 所有键
print(d1.values()) # 所有值
print(d1.items()) # 所有键值对
print('a' in d1) # 键存在检查
# 6. 字典遍历
for key in d1:
print(key, d1[key])
for key, value in d1.items():
print(key, value)
# 7. 字典推导式
squares = {x: x**2 for x in range(5)}
# 8. 默认字典
from collections import defaultdict
dd = defaultdict(int)
dd['a'] += 1 # 自动初始化为0
# 9. 合并字典
merged = {**d1, **d2}
# 10. 有序字典
from collections import OrderedDict
od = OrderedDict()
od['a'] = 1
od['b'] = 2
普通字典{}
# 创建
empty_dict = {}
dict_with_values = {'a': 1, 'b': 2}
# 特点
- 最基础的字典实现
- 访问不存在的键会引发KeyError
- 需要手动处理键不存在的情况
dict()
构造函数
# 创建方式
d1 = dict(a=1, b=2) # 关键字参数
d2 = dict([('a',1), ('b',2)]) # 可迭代对象
d3 = dict(zip(['a','b'], [1,2])) # zip对象
# 适用场景
- 从其他数据结构转换
- 动态构建字典
- 键包含特殊字符时
defaultdict
from collections import defaultdict
# 创建
dd = defaultdict(int) # 默认值0
dd = defaultdict(list) # 默认值空列表
# 特点
- 自动处理缺失键
- 需要指定默认值工厂函数
- 适合统计、分组场景
使用建议
1.
优先使用 {} 字面量语法创建简单字典
2.
需要从其他数据结构转换时使用 dict()
3.
频繁处理可能缺失的键时使用 defaultdict
4.
注意 defaultdict 的内存消耗可能更大
函数
不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
def ChangeInt( a ):
a = 10
b = 2
ChangeInt(b)
print b # 结果是 2
# 可写函数说明
def changeme( mylist ):
"修改传入的列表"
mylist.append([1,2,3,4])
print "函数内取值: ", mylist
return
# 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print "函数外取值: ", mylist
'''
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
函数外取值: [10, 20, 30, [1, 2, 3, 4]]
'''
参数类型
def example_func(required_arg, *args, default_arg='default', **kwargs):
"""
参数类型演示函数
:param required_arg: 必备参数
:param args: 不定长位置参数
:param default_arg: 默认参数
:param kwargs: 不定长关键字参数
"""
print(f"必备参数: {required_arg}")
print(f"默认参数: {default_arg}")
print(f"不定长位置参数: {args}")
print(f"不定长关键字参数: {kwargs}")
# 1. 必备参数调用
print("--- 必备参数调用 ---")
example_func('必须提供') # 最少需要一个必备参数
# 2. 默认参数调用
print("\n--- 默认参数调用 ---")
example_func('必须提供', default_arg='修改默认值')
# 3. 不定长位置参数(*args)
print("\n--- 不定长位置参数 ---")
example_func('必须提供', '额外1', '额外2', '额外3')
# 4. 不定长关键字参数(**kwargs)
print("\n--- 不定长关键字参数 ---")
example_func('必须提供', key1='值1', key2='值2')
# 5. 混合所有参数类型
print("\n--- 混合所有参数类型 ---")
example_func('必须提供', '额外1', '额外2',
default_arg='修改默认',
name='Alice', age=25)
匿名函数
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print "相加后的值为 : ", sum( 10, 20 )
print "相加后的值为 : ", sum( 20, 20 )
导包
# 1. 基本导入整个模块
import math
print(math.sqrt(16)) # 4.0
# 2. 导入模块并设置别名
import numpy as np
print(np.array([1,2,3])) # [1 2 3]
# 3. 从模块导入特定函数/类
from datetime import datetime
print(datetime.now()) # 当前时间
# 4. 从模块导入多个对象
from os import path, mkdir
print(path.exists('test.txt'))
# 5. 导入模块中的所有对象(不推荐)
from random import *
print(randint(1,10)) # 1-10随机数
# 6. 相对导入(在包内部使用)
# from . import module_in_same_dir
# from .. import module_in_parent_dir
# 7. 动态导入
module_name = "json"
json = __import__(module_name)
print(json.dumps({"key":"value"})) # {"key":"value"}
# 8. 使用importlib(更灵活的导入方式)
import importlib
re = importlib.import_module("re")
print(re.search(r"\d+","abc123").group()) # 123
# 9. 导入子模块
import urllib.request
print(urllib.request.urlopen("http://example.com"))
# 10. 条件导入
try:
import pandas as pd
except ImportError:
print("pandas not installed")
# 11. 导入时执行初始化代码
# 在模块中定义 __all__ 变量控制 from module import * 的行为
# 12. 导入第三方包
# 需要先安装: pip install requests
import requests
print(requests.get("http://example.com").status_code)
相对导包: 使用 .
(当前包) 和 ..
(父包) 来指定路径。例如 from . import module
或 from ..package import module
。
方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
import module |
命名空间清晰 | 访问时需要前缀 | 常用标准库 |
import module as alias |
简化长模块名 | 需要记住别名 | numpy / pandas 等 |
from module import name |
直接访问 | 可能命名冲突 | 常用函数/类 |
from module import * |
方便快捷 | 污染命名空间 | 不推荐使用 |
相对导入 | 包内模块引用便捷 | 只能用于包内部 | 项目内部模块 |
动态导入 | 运行时决定导入内容 | 代码可读性差 | 插件系统等 |
IO
文件IOmode
参数记忆
# 基础模式
'r' # 只读(read) - 文件必须存在
'w' # 只写(write) - 创建新文件或清空已有文件
'a' # 追加(append) - 在文件末尾添加内容
'x' # 排他创建(exclusive) - 文件必须不存在
# 组合模式
'r+' # 读写(read+) - 文件必须存在
'w+' # 读写(write+) - 创建新文件或清空已有文件
'a+' # 读写(append+) - 在文件末尾添加内容
# 二进制模式(加b)
'rb' # 二进制只读
'wb' # 二进制只写
'ab' # 二进制追加
# 记忆口诀:
# "rwax"是基础,加"+"变读写,加"b"变二进制
# 1. 文件基本读写
# 写入文件
with open('example.txt', 'w', encoding='utf-8') as f:
f.write('第一行文本\n') # 创建example.txt文件并写入内容
f.write('第二行文本\n') # 追加第二行内容
# 读取文件
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read()
print('文件全部内容:', content) # 输出: 文件全部内容: 第一行文本\n第二行文本\n
# 2. 逐行读取
with open('example.txt', 'r', encoding='utf-8') as f:
for line in f:
print('逐行读取:', line.strip()) # 输出: 逐行读取: 第一行文本\n逐行读取: 第二行文本
#line可以换成任意变量名
#strip()用于移除字符串开头和结尾的空白字符
# 3. 二进制文件操作
# 写入二进制数据
with open('binary.bin', 'wb') as f:
f.write(b'\x48\x65\x6c\x6c\x6f') # 创建binary.bin文件并写入二进制数据
# 读取二进制数据
with open('binary.bin', 'rb') as f:
data = f.read()
print('二进制数据:', data) # 输出: 二进制数据: b'Hello'
# 4. 文件指针操作
with open('example.txt', 'r+', encoding='utf-8') as f:
f.seek(0) # 移动到文件开头
print('当前位置:', f.tell()) # 输出: 当前位置: 0
f.seek(5) # 移动到第5个字节
print('从位置5读取:', f.read(3)) # 输出: 从位置5读取: 行文
# 5. 追加模式
with open('example.txt', 'a', encoding='utf-8') as f:
f.write('追加的内容\n') # 在文件末尾追加新内容
# 6. 内存IO(StringIO/BytesIO)
from io import StringIO, BytesIO
# StringIO
string_io = StringIO()
string_io.write('内存中的文本') # 向内存缓冲区写入内容
string_io.seek(0)
print('StringIO内容:', string_io.read()) # 输出: StringIO内容: 内存中的文本
# BytesIO
bytes_io = BytesIO()
bytes_io.write(b'\x01\x02\x03') # 向内存缓冲区写入二进制数据
bytes_io.seek(0)
print('BytesIO内容:', bytes_io.read()) # 输出: BytesIO内容: b'\x01\x02\x03'
# 7. 文件系统操作
import os
import shutil
# 检查文件是否存在
print('文件存在:', os.path.exists('example.txt')) # 输出: 文件存在: True
# 获取文件大小
print('文件大小:', os.path.getsize('example.txt'), 'bytes') # 输出: 文件大小: 36 bytes
# 重命名文件
os.rename('example.txt', 'renamed.txt') # 文件重命名为renamed.txt
# 复制文件
shutil.copy('renamed.txt', 'copy.txt') # 创建copy.txt副本
# 删除文件
os.remove('copy.txt') # 删除copy.txt文件
# 8. 目录操作
# 创建目录
os.makedirs('test_dir', exist_ok=True) # 创建test_dir目录
# 列出目录内容
print('目录内容:', os.listdir('.')) # 输出: 目录内容: ['renamed.txt', 'binary.bin', 'test_dir']
# 删除目录
os.rmdir('test_dir') # 删除test_dir目录
# 9. 路径操作
from os import path
print('路径拼接:', path.join('dir', 'file.txt')) # 输出: 路径拼接: dir\file.txt
print('绝对路径:', path.abspath('renamed.txt')) # 输出: 绝对路径: C:\Users\a1494\Desktop\Project\renamed.txt
print('路径分割:', path.split('/path/to/file.txt')) # 输出: 路径分割: ('/path/to', 'file.txt')
print('扩展名:', path.splitext('file.txt')) # 输出: 扩展名: ('file', '.txt')
# 10. 临时文件
import tempfile
# 创建临时文件
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write(b'temporary data') # 创建临时文件并写入数据
tmp_path = tmp.name
print('临时文件路径:', tmp_path) # 输出: 临时文件路径: C:\Users\a1494\AppData\Local\Temp\tmpXXXXXX
# 删除临时文件
os.unlink(tmp_path) # 删除临时文件
# 11. 序列化与反序列化
import pickle
# 序列化到文件
data = {'key': 'value', 'num': 42}
with open('data.pkl', 'wb') as f:
pickle.dump(data, f) # 创建data.pkl文件并序列化数据
# 从文件反序列化
with open('data.pkl', 'rb') as f:
loaded = pickle.load(f)
print('反序列化数据:', loaded) # 输出: 反序列化数据: {'key': 'value', 'num': 42}
# 12. JSON文件操作
import json
# 写入JSON
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2) # 创建data.json文件并写入格式化JSON
# 读取JSON
with open('data.json', 'r', encoding='utf-8') as f:
json_data = json.load(f)
print('JSON数据:', json_data) # 输出: JSON数据: {'key': 'value', 'num': 42}
# 13. CSV文件操作
import csv
# 写入CSV
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '年龄', '城市']) # 写入表头
writer.writerow(['张三', 25, '北京']) # 写入第一行数据
writer.writerow(['李四', 30, '上海']) # 写入第二行数据
# 读取CSV
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print('CSV行:', row) # 输出: CSV行: ['姓名', '年龄', '城市']\nCSV行: ['张三', '25', '北京']\nCSV行: ['李四', '30', '上海']
# 14. 标准输入输出
print('标准输出') # 输出: 标准输出
user_input = input('请输入: ') # 等待用户输入
print('你输入了:', user_input) # 输出: 你输入了: [用户输入的内容]
# 15. 上下文管理器
'''
上下文管理器(Context Manager)是Python中用于管理资源(如文件、网络连接、数据库连接等)的一种机制,它确保资源在使用后被正确释放,即使在发生异常的情况下也是如此。
'''
class MyFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
with MyFile('custom.txt') as f:
f.write('自定义上下文管理器') # 创建custom.txt文件并写入内容
异常
# 1. 基本try-except结构
try:
result = 10 / 0 # 可能引发ZeroDivisionError
except ZeroDivisionError:
print("不能除以零")
# 2. 捕获多个异常
try:
value = int("abc") # 可能引发ValueError
except (ValueError, TypeError) as e:
print(f"输入错误: {e}")
# 3. 捕获所有异常
try:
with open("nonexist.txt") as f: # 可能引发FileNotFoundError
content = f.read()
except Exception as e:
print(f"发生错误: {type(e).__name__}: {e}")
# 4. try-except-else结构
try:
num = int(input("请输入数字: "))
except ValueError:
print("输入的不是有效数字")
else:
print(f"输入的数字是: {num}")
# 5. try-except-finally结构
try:
f = open("data.txt", "r")
data = f.read()
except IOError:
print("文件读取失败")
finally:
f.close() # 确保文件总是被关闭
# 6. 自定义异常
class MyError(Exception):
pass
try:
raise MyError("自定义错误")
except MyError as e:
print(f"捕获自定义异常: {e}")
# 7. 异常链
try:
try:
raise ValueError("原始错误")
except ValueError as e:
raise RuntimeError("新错误") from e
except RuntimeError as e:
print(f"捕获异常: {e}")
print(f"原始异常: {e.__cause__}")
# 8. 断言异常
try:
age = -1
assert age >= 0, "年龄不能为负数"
except AssertionError as e:
print(f"断言失败: {e}")
面向对象
# 1. 基本类定义与实例化
class Animal:
# 类属性
kingdom = "Animalia"
# 构造方法
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age
# 实例方法
def speak(self):
print(f"{self.name} makes a sound")
# 类方法
@classmethod
def get_kingdom(cls):
return cls.kingdom
# 静态方法
@staticmethod
def is_animal(obj):
return isinstance(obj, Animal)
# 2. 继承与多态
class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age) # 调用父类构造方法
self.breed = breed
# 方法重写
def speak(self):
print(f"{self.name} says: Woof!")
# 扩展新方法
def fetch(self, item):
print(f"{self.name} fetches {item}")
# 3. 封装与属性控制
class BankAccount:
def __init__(self, owner, balance=0):
self._owner = owner # 保护属性
self.__balance = balance # 私有属性
# 属性装饰器
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("余额不能为负")
self.__balance = value
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("余额不足")
self.__balance -= amount
# 4. 抽象类与接口
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# 5. 多重继承与MRO
class Flyable:
def fly(self):
print("Flying")
class Swimmable:
def swim(self):
print("Swimming")
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name, age):
super().__init__(name, age)
def speak(self):
print(f"{self.name} says: Quack!")
# 6. 魔术方法
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __len__(self):
return 2
# 7. 类组合
class Engine:
def start(self):
print("Engine started")
class Car:
def __init__(self, model):
self.model = model
self.engine = Engine()
def start(self):
self.engine.start()
print(f"{self.model} is ready to go")
# 8. 枚举类
from enum import Enum, auto
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
# 9. 数据类
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
z: float = 0.0 # 默认值
# 使用示例
if __name__ == "__main__":
# 基本类
cat = Animal("Whiskers", 3)
cat.speak()
# 继承
dog = Dog("Buddy", 5, "Golden Retriever")
dog.speak()
dog.fetch("ball")
# 封装
account = BankAccount("Alice", 1000)
account.deposit(500)
print(f"Balance: {account.balance}")
# 多态
animals = [cat, dog]
for animal in animals:
animal.speak()
# 抽象类
rect = Rectangle(4, 5)
print(f"Area: {rect.area()}")
# 多重继承
duck = Duck("Donald", 2)
duck.speak()
duck.fly()
duck.swim()
# 魔术方法
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)
# 组合
car = Car("Tesla")
car.start()
# 枚举
print(Color.RED)
# 数据类
p = Point(1.0, 2.0)
print(p)
@property
property 的有两个作用
- 作为装饰器 @property 将类方法转换为类属性(只读)
- property 重新实现一个属性的 setter 和 getter 方法
这种设计模式常用于:
- 控制属性的访问和修改
- 保持API简洁
- 添加属性访问的逻辑控制
@property 将类方法转换为只读属性(常用)
class Circle(object):
__pi = 3.14
def __init__(self, r):
self.r = r
@property
def pi(self):
return self.__pi
circle1 = Circle(2)
print(circle1.pi)
circle1.pi=3.14159 # 出现AttributeError异常
property 重新实现 setter 和 getter 方法(少用)
class Circle(object):
__pi = 3.14
def __init__(self, r):
self.r = r
@property
def pi(self):
return self.__pi
@pi.setter
def pi(self, pi):
Circle.__pi = pi
circle1 = Circle(2)
circle1.pi = 3.14 # 设置 pi的值
print(circle1.pi) # 访问 pi的值
把一个方法变成属性,只需要加上 @property
就可以了,如上此时 pi(self)
方法,@property
本身又创建了另一个装饰器 @pi.setter
,负责用 setter 方法给属性赋值,于是,将@pi.setter
加到pi(self, pi)
上,我们就拥有一个可控的属性操作。
在Python
中, 没有 @pi.getter
这种直接的装饰器语法 。@property 装饰器本身已经隐式创建了getter
,直接使用 @property
就相当于getter
,只有需要额外定义setter
或deleter
时才需要显式声明
@classmethod、@staticmethod 装饰方法
class DateUtil:
# 类属性
DATE_FORMAT = "%Y-%m-%d"
def __init__(self, date_str):
self.date = datetime.strptime(date_str, self.DATE_FORMAT)
@classmethod
def from_timestamp(cls, timestamp):
"""类方法:通过时间戳创建实例"""
date_obj = datetime.fromtimestamp(timestamp)
return cls(date_obj.strftime(cls.DATE_FORMAT)) # 调用构造函数
@staticmethod
def is_valid(date_str):
"""静态方法:验证日期格式"""
try:
datetime.strptime(date_str, DateUtil.DATE_FORMAT)
return True
except ValueError:
return False
def get_year(self):
"""实例方法"""
return self.date.year
# 使用示例
from datetime import datetime
# 1. 类方法调用
new_year = DateUtil.from_timestamp(1672531200) # 2023-01-01
print(new_year.get_year()) # 输出: 2023
# 2. 静态方法调用
print(DateUtil.is_valid("2023-13-01")) # 输出: False
print(DateUtil.is_valid("2023-12-01")) # 输出: True
# 3. 实例方法调用
date = DateUtil("2023-01-01")
print(date.get_year()) # 输出: 2023
关键区别说明
@classmethod :
- 第一个参数是 cls (类本身)
- 可以访问和修改类属性
- 常用于替代构造函数(多态构造)
- 示例中 from_timestamp 将时间戳转为日期对象
@staticmethod :
- 不需要 self 或 cls 参数
- 不能访问类或实例属性
- 相当于放在类里的普通函数
- 示例中 is_valid 只做格式验证
何时使用 :
- 需要访问类属性或创建实例 → @classmethod
- 纯工具函数,与类相关但不依赖类状态 → @staticmethod
- 需要访问实例属性 → 普通实例方法
cls和self的区别
cls :
- 用于 @classmethod 装饰的类方法
- 代表类本身(如 Date 类)
- 用于创建新实例或访问类属性
self :
- 用于实例方法
- 代表类的实例对象
- 用于访问实例属性和方法
关键区别 :
- cls 用于类级别操作, self 用于实例级别操作
- 类方法可以通过 cls() 创建新实例,实例方法不能
- 技术上二者可以随便写,但会:
- 造成代码混淆
- 失去类方法特性
- 违反PEP8规范
类中的访问限制
1.命名约定 :
- __xx : 严格私有(双下划线开头)
- _xx : 保护成员(单下划线开头,约定不应直接访问)
- xx : 公开成员
2.私有成员访问 :
- 通过 _类名__xx 可强制访问但不推荐
- 应通过类提供的公共接口访问
3.方法限制 :
- 私有方法同样适用双下划线规则
- 内部方法可用单下划线表示保护
4.最佳实践 :
- 敏感数据应设为私有
- 通过方法提供可控访问
- 避免直接访问 _xx 和 __xx 成员
成员指属性和方法
class BankAccount:
# 类公开属性
bank_name = "中国银行" # 公开属性(无下划线)
# 类保护属性
_bank_code = "BOC" # 保护属性(单下划线)
# 类私有属性
__interest_rate = 0.03 # 私有属性(双下划线)
def __init__(self, account_id, balance):
# 实例公开属性
self.account_id = account_id # 公开属性
# 实例保护属性
self._balance = balance # 保护属性
# 实例私有属性
self.__password = "123456" # 私有属性
# 公开方法
def show_balance(self):
return f"账户 {self.account_id} 余额: {self._balance}"
# 保护方法
def _calculate_interest(self):
return self._balance * self.__interest_rate
# 私有方法
def __verify_password(self, input_pwd):
return self.__password == input_pwd
# 通过公开方法访问私有成员
def withdraw(self, amount, pwd):
if not self.__verify_password(pwd):
return "密码错误"
if amount > self._balance:
return "余额不足"
self._balance -= amount
return f"取款成功,余额: {self._balance}"
# 使用示例
account = BankAccount("888888", 1000)
# 1. 访问公开成员
print(account.bank_name) # 正常访问
print(account.show_balance()) # 正常访问
print(account.account_id) # 正常访问
# 2. 访问保护成员(不推荐但可以)
print(account._bank_code) # 不推荐
print(account._balance) # 不推荐
print(account._calculate_interest()) # 不推荐
# 3. 访问私有成员(直接访问会报错)
try:
print(account.__interest_rate) # AttributeError
except Exception as e:
print(f"错误: {e}")
try:
print(account.__verify_password("123")) # AttributeError
except Exception as e:
print(f"错误: {e}")
# 4. 强制访问私有成员(不推荐)
print(f"强制访问利率: {account._BankAccount__interest_rate}")
print(f"强制验证密码: {account._BankAccount__verify_password('123456')}")
# 5. 正确通过公开方法访问
print(account.withdraw(500, "123456")) # 正确方式
print(account.withdraw(500, "111111")) # 密码错误
抽象类和接口类
抽象类
- 抽象方法可以有文档字符串
- Python 3.4+也可以使用class 类名(abc.ABC)简化写法
语法
import abc # 必须导入abc模块
class 类名(metaclass=abc.ABCMeta): # 使用ABCMeta元类
@abc.abstractmethod # 抽象方法装饰器
def 方法名(self):
pass # 只有方法定义没有实现
@property # 也可以是抽象属性
@abc.abstractmethod
def 属性名(self):
pass
import abc
class Animal(metaclass=abc.ABCMeta): # 抽象基类
@abc.abstractmethod
def make_sound(self): # 抽象方法
'所有动物都必须能发出声音'
pass
@abc.abstractmethod
def move(self): # 抽象方法
'所有动物都必须能移动'
pass
class Dog(Animal): # 子类必须实现所有抽象方法
def make_sound(self):
print("汪汪汪!")
def move(self):
print("狗用四条腿跑")
class Bird(Animal): # 另一个子类
def make_sound(self):
print("叽叽喳喳!")
def move(self):
print("鸟用翅膀飞")
# 使用示例
dog = Dog()
bird = Bird()
dog.make_sound() # 输出: 汪汪汪!
bird.move() # 输出: 鸟用翅膀飞
# 不能直接实例化抽象类
try:
animal = Animal() # 会报错: TypeError
except TypeError as e:
print(f"错误: {e}")
接口类
import abc
class 接口类名(metaclass=abc.ABCMeta): # 使用ABCMeta元类
@abc.abstractmethod # 抽象方法装饰器
def 方法名(self):
pass # 只有方法定义没有实现
用abc模块装饰后,在实例化的时候就会报错,那么当我们代码很长的时候,就可以早一点预知错误,所以以后在接口类类似问题中用这个模块
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,
可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
# 单继承示例
class Animal: # 基类
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}正在吃东西")
class Dog(Animal): # 单继承
def bark(self):
print(f"{self.name}在汪汪叫")
# 多继承示例
class Flyable: # 第一个父类
def fly(self):
print(f"{self.name}在飞")
class Swimmable: # 第二个父类
def swim(self):
print(f"{self.name}在游泳")
class Duck(Animal, Flyable, Swimmable): # 多继承
def quack(self):
print(f"{self.name}在嘎嘎叫")
# 使用示例
dog = Dog("小黑")
dog.eat() # 继承自Animal: 小黑正在吃东西
dog.bark() # 自己的方法: 小黑在汪汪叫
print("----")
duck = Duck("小黄")
duck.eat() # 继承自Animal: 小黄正在吃东西
duck.fly() # 继承自Flyable: 小黄在飞
duck.swim() # 继承自Swimmable: 小黄在游泳
duck.quack() # 自己的方法: 小黄在嘎嘎叫
# 查看方法解析顺序(MRO)
print(Duck.__mro__)
# 输出: (<class '__main__.Duck'>, <class '__main__.Animal'>,
# <class '__main__.Flyable'>, <class '__main__.Swimmable'>, <class 'object'>)
总结
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性
1.抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
2.在继承抽象类的过程中,我们应该尽量避免多继承;
3.而在继承接口的时候,我们反而鼓励你来多继承接口
一般情况下 单继承 能实现的功能都是一样的,所以在父类中可以有一些简单的基础实现,多继承的情况 由于功能比较复杂,所以不容易抽象出相同的功能的具体实现写在父类中
1.抽象类介于类和接口之间
- 抽象类可以包含具体实现的方法(像普通类)
- 也可以包含抽象方法(像接口)
- 例如Python中的 abc.ABC 允许定义抽象方法但也可以包含具体方法
- 归一化设计指通过抽象类强制子类实现统一的方法签名
2.避免多继承抽象类
- 抽象类可能包含具体实现和状态
- 多继承容易导致方法冲突(Diamond Problem)
- 例如:两个抽象父类有同名方法时子类无法确定继承哪个
3.鼓励多继承接口
- 接口只定义方法签名没有实现
- 多继承接口不会引入实现冲突
- 例如Java的类可以实现多个接口
关于单继承与多继承的补充说明:
- 单继承时父类可以提供基础实现,子类直接复用
- 多继承场景功能复杂,很难在父类中提供通用实现
- 例如: class A(B,C,D) 如果B,C,D都有 foo() 方法,A无法确定使用哪个
代码示例说明差异:
from abc import ABC, abstractmethod
# 抽象类(不推荐多继承)
class Animal(ABC):
@abstractmethod
def move(self): pass
def sleep(self): # 具体实现
print("睡觉中")
# 接口风格(鼓励多继承)
class Flyable(ABC):
@abstractmethod
def fly(self): pass
class Swimmable(ABC):
@abstractmethod
def swim(self): pass
# 正确做法:单继承抽象类+多继承接口
class Duck(Animal, Flyable, Swimmable):
def move(self): print("走路")
def fly(self): print("飞行")
def swim(self): print("游泳")
魔术方法
在Python中,所有以双下划线__
包起来的方法,统称为Magic Method(魔术方法),它是一种的特殊方法,普通方法需要调用,而魔术方法不需要调用就可以自动执行。
魔术方法无法自定义,只能根据python提供好的魔术方法修改
魔术方法在类或对象的某些事件出发后会自动执行,让类具有神奇的“魔力”。如果希望根据自己的程序定制自己特殊功能的类,那么就需要对这些方法进行重写。
Python中常用的运算符、for循环、以及类操作等都是运行在魔术方法之上的。
class Vector:
"""演示魔术方法的向量类"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# 对象表示
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
# 算术运算
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# 比较运算
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
# 容器模拟
def __len__(self):
return 2 # 固定长度为2
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
raise IndexError("Vector index out of range")
# 上下文管理
def __enter__(self):
print(f"进入向量上下文: {self}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"退出向量上下文: {self}")
# 属性访问
def __getattr__(self, name):
if name == "magnitude":
return (self.x**2 + self.y**2)**0.5
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# 调用模拟
def __call__(self, scale=1):
"""使实例可调用,返回缩放后的向量"""
return Vector(self.x * scale, self.y * scale)
# 使用示例
v1 = Vector(2, 3)
v2 = Vector(5, 7)
# 对象表示
print(repr(v1)) # Vector(2, 3)
print(str(v1)) # (2, 3)
# 算术运算
print(v1 + v2) # (7, 10)
print(v1 * 3) # (6, 9)
# 比较运算
print(v1 == v2) # False
print(v1 < v2) # True
# 容器模拟
print(len(v1)) # 2
print(v1[0]) # 2
# 动态属性
print(v1.magnitude) # 3.605551275463989
# 调用模拟
scaled = v1(2)
print(scaled) # (4, 6)
# 上下文管理
with Vector(1, 1) as v:
print(v.x + v.y) # 2
这个示例演示了以下魔术方法:
- init : 对象初始化
- repr / str : 对象字符串表示
- add / sub / mul : 算术运算
- eq / lt : 比较运算
- len / getitem : 容器模拟
- enter / exit : 上下文管理
- getattr : 动态属性访问
- call : 使实例可调用
枚举类
枚举类(Enum)在Python中的主要作用包括:
替代魔法数字/字符串
- 用有意义的名称代替数字或字符串常量
- 示例:用 Color.RED 代替 1 或 "red"
类型安全
- 限制变量只能取预定义的值
- 防止拼写错误和无效值
代码可读性
- 使代码更易于理解和维护
- 示例: Status.PENDING 比 1 更直观
可迭代性
- 可以遍历所有枚举值
- 示例: for color in Color:
防止重复
- 确保枚举值唯一
- 自动处理值冲突
from enum import Enum, auto
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
def describe(self):
return f"{self.name}的颜色代码是{self.value}"
class Status(Enum):
PENDING = auto() # 自动分配值
APPROVED = auto()
REJECTED = auto()
# 使用示例
print(Color.RED.describe()) # 输出: RED的颜色代码是1
print(list(Status)) # 输出: [<Status.PENDING: 1>, ...]
# 类型安全示例
def set_status(s: Status):
print(f"设置状态为: {s}")
set_status(Status.APPROVED) # 正确
# set_status(4) # 会报错
实际应用场景:
- 状态机(如订单状态)
- 配置选项(如日志级别)
- 有限选项(如星期、月份)
- 错误代码定义
数据类
在 Python 中,dataclasses
模块是一个非常有用的工具,它简化了类的定义,特别是当类主要用于存储数据时。传统的 Python 类通常需要编写大量的初始化方法、比较方法等来管理数据,而 dataclasses
提供了一种简洁且自动化的方式来完成这些任务。
from dataclasses import dataclass, field, asdict, astuple
from typing import List, ClassVar, Optional
import json
@dataclass(order=True) # 启用排序功能
class Person:
# 类变量
species: ClassVar[str] = "Human"
# 实例变量
name: str
age: int
email: Optional[str] = None # 可选字段
hobbies: List[str] = field(default_factory=list) # 可变默认值
# 后初始化处理
def __post_init__(self):
if self.age < 0:
raise ValueError("Age cannot be negative")
# 实例方法
def greet(self):
return f"Hello, I'm {self.name}, {self.age} years old"
# 使用示例
p1 = Person("Alice", 25, "alice@example.com", ["reading", "hiking"])
p2 = Person("Bob", 30)
# 自动生成的__repr__
print(p1) # Person(name='Alice', age=25, email='alice@example.com', hobbies=['reading', 'hiking'])
# 自动生成的__eq__
print(p1 == p2) # False
# 排序功能(因为设置了order=True)
print(sorted([p2, p1])) # [Person(name='Alice', ...), Person(name='Bob', ...)]
# 转换为字典
print(asdict(p1))
# {'name': 'Alice', 'age': 25, 'email': 'alice@example.com', 'hobbies': ['reading', 'hiking']}
# 转换为元组
print(astuple(p1))
# ('Alice', 25, 'alice@example.com', ['reading', 'hiking'])
# 类变量访问
print(Person.species) # Human
print(p1.species) # Human
# JSON序列化
print(json.dumps(asdict(p1)))
# {"name": "Alice", "age": 25, "email": "alice@example.com", "hobbies": ["reading", "hiking"]}
# 继承示例
@dataclass
class Employee(Person):
employee_id: str
salary: float = 0.0
e = Employee("Charlie", 35, "charlie@company.com", [], "EMP001", 5000.0)
print(e.greet()) # Hello, I'm Charlie, 35 years old
装饰器/语法糖
装饰器就是一个函数,这个函数接受一个参数,这个参数是一个函数,然后返回一个新的函数。
这个新函数通常会“包裹”住原始函数,在调用原始函数之前或之后执行一些额外的代码。
闭包就是装饰器里那个负责装饰作用的函数,但是没有实例,和面向对象的对象有点像
装饰器是一个工厂,它接收一个旧函数,生产出一个新函数。而这个被生产出来的、负责执行所有具体装饰行为的新函数,就是一个闭包。
下面这个例子中,simple_decorator
是装饰器,wrapper
是闭包,closure
是闭包实例
def simple_decorator(func): # 传入一个函数作为参数
def wrapper():
print("即将执行函数...")
func()
print("函数执行完毕。")
return wrapper # 返回wrapper
def greet():
print("你好!")
closure = simple_decorator(greet)
语法糖就是用来优化可读性的,是一个概念,在任何编程语言里都可以有
参考
Python 类进阶:私有属性/方法/装饰器/property - 知乎
2316-Python 数据类(dataclasses):简化类定义和数据管理 - 知乎
requests
post请求中json
和data
参数不能同时出现
flask
杂
使用render_template
函数会在Flask 应用所在的根目录中寻找一个名为 templates
的文件夹。但是也可以更改这个行为
#在创建 Flask 应用实例时指定模板文件夹
from flask import Flask
app = Flask(__name__, template_folder='my_templates')
#在蓝图(Blueprint)中指定模板文件夹
from flask import Blueprint
account_bp = Blueprint('account', __name__, template_folder='templates')
蓝图是 Flask 中的组织代码的方式。它允许你将相关的视图函数、模板和静态文件组织在一起,并且可以在多个应用中重用。
from flask import Blueprint
bp = Blueprint('main', __name__)
@bp.route('/')
def home():
return 'Home Page'
from flask import Flask
from .routes import bp as main_bp
def create_app():
app = Flask(__name__)
app.register_blueprint(main_bp)
return app
线程池使用from dbutils.pooled_db import PooledDB
Flask 应用中的 __name__
作用:告诉 Flask 你的应用主程序在哪里,以便它能正确地定位和加载资源,如模板(templates
)和静态文件(static
)。
原理:
- 当作为主程序运行时,
__name__
变量的值是'__main__'
。 - 当被导入时,
__name__
的值是模块的文件名(例如'app'
)。
最佳实践:在实例化 Flask 应用时,始终使用 app = Flask(__name__)
。这保证了无论你的代码如何被调用,路径都能正确解析。
一个例子来理解
假设你的项目目录如下:
/project
|-- app.py
|-- run.py
|-- templates/
| |-- index.html
情况一:直接运行 app.py
# app.py
from flask import Flask
app = Flask(__name__) # 此时 __name__ = '__main__'
当你执行 python app.py
时,__name__
变量的值是 '__main__'
。Flask
会以 app.py
所在的目录 /project
作为根目录,去寻找 templates
文件夹。
情况二:从 run.py
导入 app.py
# run.py
from app import app
# ...
当你执行 python run.py
时,Python 解释器在导入 app
模块时,会把 app.py
里的 __name__
变量设置为 'app'
。Flask
框架会根据这个模块名,找到 app.py
所在的路径 /project
,并将其作为根目录来查找资源。
form
表单的流量(数据传输)
- 客户端:用户在 HTML
<form>
表单中输入数据,点击“提交”按钮。 - 浏览器:根据
<form>
标签的method
和action
属性,构建一个 HTTP 请求。method="post"
:使用 HTTPPOST
方法,将数据放在请求体中发送。action="/login"
:将请求发送到服务器的/login
路径。
- 请求体格式:数据被编码为
key=value&key=value
的字符串,例如role=1&mobile=13800138000
。 - 服务器端:后端应用(如 Flask)接收请求,从请求体中解析出数据,进行处理。
服务器渲染
例子
<span>{{ error }}</span>
当浏览器请求一个网页时,服务器会执行以下步骤:
- 接收请求:服务器接收到来自浏览器的 HTTP 请求(例如,请求
/login
页面)。 - 后端处理:服务器端的代码(例如,用 Python/Flask 编写的后端应用)会处理这个请求。它可能会执行一些业务逻辑,比如检查用户登录信息、从数据库查询数据或者,在这个例子中,生成一个错误消息。
- 渲染模板:后端应用会加载一个 HTML 文件(称为模板,如
login.html
)。这个模板文件不是一个普通的静态 HTML,它包含了一些特殊的占位符,比如{{ error }}
。 - 变量替换:后端应用会将它在第 2 步中准备好的数据(例如,
error
变量的值是"用户名或密码错误"
)填充到模板中的相应占位符里。这个过程就叫做模板渲染。 - 发送响应:渲染完成后,服务器会把一个完整的、静态的 HTML 页面发送回给浏览器。这个 HTML 页面已经不包含任何
{{ ... }}
占位符了,所有变量都已经被替换成了具体的值。
静态资源的路径问题
路径的确定:
- 浏览器:将 HTML 中的绝对路径(以
/
开头)与网站根目录拼接成完整的 URL。 link
和script
标签:href
或src
属性中的路径是浏览器要请求的 URL,而不是服务器上的文件系统路径。
Flask 静态文件映射:
- Flask 默认将项目中的
static
文件夹映射到 URL 路径/static
。 - 在 HTML 中引用静态文件时,应使用
/static/
开头的绝对路径,例如<script src="/static/js/jquery.js"></script>
。
cookie问题
设置完cookie
后,抓一下包会发现在第一个包里就设置了cookie
。
因为cookie是由下面的代码产生的,这个代码又是在longin
的路径下,我以为是在发包之后才会设置,但是事实告诉我不是。
if user_dict:
# 登录成功,跳转
session["user_info"] = {"role": user_dict['role'], 'real_name': user_dict['real_name'], 'id': user_dict['id']}
return redirect('/order/list')
gemini
是这样回答的
你提供的 POST 请求头显示,尽管这是第一次登录,但请求中已经带有了
session
cookie,这是因为 Flask 的 session 机制。Flask 的 Session 工作原理
当你访问一个 Flask 应用时,即使你还没有登录,Flask 也可能在第一次响应中给你发送一个包含 空会话数据 的
session
cookie。这个 cookie 只是一个标识,说明你的浏览器和服务器之间已经建立了一个会话。整个过程是这样的:
- 第一次访问 (
GET /login
):当你第一次访问登录页面 (GET /login
) 时,Flask 应用会生成一个会话 ID,并创建一个空的会话对象。然后,它会在响应头中添加一个Set-Cookie
头,将这个空的会话信息发送给你的浏览器。
- 浏览器收到响应:浏览器接收到这个
Set-Cookie
响应头后,就会将session
cookie 存储起来。- 提交登录表单 (
POST /login
):当你填写表单并点击提交时,你的浏览器会自动将之前存储的session
cookie 包含在 POST 请求头 中发送给服务器。
- 服务器处理请求:服务器收到请求后,会读取这个
session
cookie,并根据SECRET_KEY
验证其完整性。- 登录成功:在你的登录视图函数中,你执行了
session["user_info"] = ...
,这时 Flask 会将用户数据添加到 当前会话对象 中。- 新的响应:当视图函数返回时,Flask 会生成一个新的
session
cookie,其中包含了更新后的用户数据(即user_info
),并在响应头中再次通过Set-Cookie
发送给浏览器。- 后续请求:从现在开始,你的浏览器在向服务器发送的每个请求中都会携带这个包含用户信息的
session
cookie,这样服务器就可以识别你的身份,并在你访问其他受保护的页面时判断你是否已登录。
所以所,第一次 POST 请求 中带有的 session
cookie,实际上是在 第一次 GET 请求 登录页面时,服务器设置的那个 空会话 cookie。它代表了浏览器和服务器之间已经建立了一个会话,服务器只是通过它来识别你这个特定的客户端。在登录成功后,服务器会更新这个会话 cookie,并在随后的响应中返回给浏览器,使其包含你的用户信息。
在另一个包里也可以看见,cookie不一样
第一次请求的cookie
Cookie: session=eyJ1c2VyX2luZm8iOnsiaWQiOjEsInJlYWxfbmFtZSI6Ilx1NWYyMFx1NGUwOSIsInJvbGUiOjJ9fQ.aJ2lRg.jJKbwoP--vJQg0krBnaC2a5VK4w
第一次以后的cookie
Cookie: session=eyJ1c2VyX2luZm8iOnsiaWQiOjEsInJlYWxfbmFtZSI6Ilx1NWYyMFx1NGUwOSIsInJvbGUiOjJ9fQ.aJ2mQw.vhbozCbUHHaz_dH9osnbXpxgTAo
重定向的数据包分析
提问:
return redirect('/order/list')
的代码为什么会产生这个数据包GET /order/list HTTP/1.1 Host: 127.0.0.1:5000 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate, br Referer: http://127.0.0.1:5000/login Connection: keep-alive Cookie: session=eyJ1c2VyX2luZm8iOnsiaWQiOjEsInJlYWxfbmFtZSI6Ilx1NWYyMFx1NGUwOSIsInJvbGUiOjJ9fQ.aJ2mQw.vhbozCbUHHaz_dH9osnbXpxgTAo Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Priority: u=0, i
回答:
return redirect('/order/list')
这行代码本身不会直接产生你看到的GET /order/list
这个数据包,而是 间接地 导致浏览器发起了这个请求。重定向的工作原理
- 服务器响应:当你的登录视图函数执行
return redirect('/order/list')
时,Flask 服务器并不会立即向/order/list
发送请求。相反,它会向你的浏览器发送一个特殊的 HTTP 302 响应。这个响应的头部包含了Location: /order/list
字段,告诉浏览器“你请求的资源不在当前位置,请去/order/list
找”。- 浏览器行为:浏览器接收到这个
HTTP 302
响应后,它会根据响应头中的Location
字段,自动发起一个新的 GET 请求 到指定的 URL,也就是/order/list
。这是一个由浏览器发起的 新请求,而不是服务器生成的。- 最终结果:你看到的数据包
GET /order/list HTTP/1.1
就是浏览器在接收到服务器的重定向响应后,为了获取/order/list
页面的内容而自动发出的。简而言之,
return redirect()
是一种服务器告诉浏览器去访问新页面的指令,而浏览器是这个新请求的实际发起者。
html模版
有时候只需要改动其中的一部分,大部分页面的某一部分都是一样的,可以用模版来
首先先创建一个模版htmllayout.html
,其中{%block body%}{%endblock%}
是要修改的部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css">
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">订单平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">订单管理 <span class="sr-only">(current)</span></a></li>
<li><a href="#">用户管理</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{real_name}} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
{%block body%}{%endblock%}
</div>
<script src="/static/jquery-3.7.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.js"></script>
</body>
</html>
然后在其他html页面中可以写如下模版,在{% block body %}
和{% endblock %}
之间就是要写的内容
{% extends 'layout.html '%}
{% block body %}
要写入的内容
{% endblock %}