1.变量的创建
因为python是一种动态语言,所以无须先声明变量类型,直接对变量赋值即可。
- 变量创建方式:变量名 = 变量值
- Python中的变量不需要声明。
每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。 - 等号(=)用来给变量赋值。
- 等号(=)运算符左边是一个变量名,等号(=)运算符右边是存储在变量中的值。
var1 = 'abc'
print(var1)
在 Python 中,你不需要预先知道或声明变量的“默认类型”,因为 Python 是动态强类型语言。
1. 类型由值决定,而非变量名
在 Python 中,变量本身没有类型,类型是属于“对象”(即值)的。当你赋值时,Python 解释器会自动根据右边的值推断出类型,并将变量指向该对象。
- 无需预设:你不需要像 Java 或 C++ 那样写
int a;或String b;。 - 自动推断:
x = 10 # x 现在指向一个整数对象 (int) x = "hello" # x 现在指向一个字符串对象 (str),之前的整数引用被释放
2. “使用前必须赋值”的含义
这句话的意思是:你不能使用一个从未被赋值过的变量名,否则会报错 NameError。
- 错误示例:
print(y) # NameError: name 'y' is not defined - 正确做法:在使用前至少赋值一次,哪怕是一个临时值。
y = None # 先赋值为 None,表示“空”或“未初始化”,但变量已创建 # ... 后续逻辑 ... y = 100 # 正式赋值 print(y)
3. 如果不确定初始值怎么办?
如果你需要在代码早期定义变量,但暂时不知道具体值,可以使用以下常见策略:
A. 使用 None
None 是 Python 中的空值对象,常用于占位。
user_name = None # 明确表示“暂无名字”,类型为 NoneType
# ... 后续获取用户输入 ...
user_name = input("请输入名字:")
B. 使用合理的默认值
根据业务逻辑赋予一个有意义的初始值。
count = 0 # 计数器默认为 0 (int)
items = [] # 列表默认为空列表 (list)
config = {} # 配置默认为空字典 (dict)
is_ready = False # 状态默认为假 (bool)
4. 如何查看当前变量的类型?
如果你想知道某个变量当前指向什么类型,可以使用内置函数 type()。
a = 10
print(type(a)) # <class 'int'>
a = "test"
print(type(a)) # <class 'str'>
总结
- 不用担心“默认类型”:Python 没有变量的默认类型,只有值的类型。
- 解决“未赋值”问题:
- 确保在使用前赋值。
- 若值未知,先用
None、0、""、[]等占位。
- 动态特性:同一个变量名在不同时刻可以指向不同类型的对象(虽然不建议频繁这样做,以免降低代码可读性)。
2.Python变量解包
变量解包(Unpacking)是Python中一个非常实用的特性,允许你将可迭代对象中的元素分别赋值给多个变量。
1. 基本解包
# 元组解包
a, b, c = (1, 2, 3)
print(a, b, c) # 1 2 3
# 列表解包
x, y, z = [10, 20, 30]
print(x, y, z) # 10 20 30
# 字符串解包
first, second = "AB"
print(first, second) # A B
2. 数量必须匹配
# 错误示例:变量数量不匹配
# a, b = (1, 2, 3) # ValueError: too many values to unpack
# a, b, c = (1, 2) # ValueError: not enough values to unpack
3. 使用星号表达式(*)捕获多个元素
# 捕获剩余部分
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
# 捕获中间部分
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# 忽略某些值(通常用_表示)
a, _, c = (1, 2, 3)
print(a, c) # 1 3
# 多个星号?不行!
# *a, *b = [1, 2, 3] # SyntaxError
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
4. 嵌套解包
# 解包嵌套结构
data = (1, (2, 3), 4)
a, (b, c), d = data
print(a, b, c, d) # 1 2 3 4
# 更复杂的嵌套
person = ["Alice", 30, ["Python", "Java"]]
name, age, [skill1, skill2] = person
print(name, age, skill1, skill2) # Alice 30 Python Java
5. 交换变量
# 经典用法:交换两个变量的值
x, y = 10, 20
x, y = y, x
print(x, y) # 20 10
# 交换多个变量
a, b, c = 1, 2, 3
a, b, c = c, a, b
print(a, b, c) # 3 1 2
6. 函数中的应用
# 函数返回多个值
def get_coordinates():
return 10, 20
x, y = get_coordinates()
print(x, y) # 10 20
# 使用*解包参数
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
result = add(*numbers) # 相当于 add(1, 2, 3) 类似JavaScript 的 ... 展开语法
print(result) # 6
# 字典解包(使用**)
def introduce(name, age):
print(f"{name} is {age} years old")
person = {"name": "Bob", "age": 25}
introduce(**person) # Bob is 25 years old
7. 循环中的解包
# 遍历元组列表
points = [(1, 2), (3, 4), (5, 6)]
for x, y in points:
print(f"({x}, {y})")
# 使用enumerate
for index, value in enumerate(["a", "b", "c"]):
print(f"{index}: {value}")
# 使用zip
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
8. 高级技巧
# 使用*忽略部分值
a, *_, c = [1, 2, 3, 4, 5]
print(a, c) # 1 5
# 同时解包多个可迭代对象
a, b, c = [1, 2, 3], [4, 5, 6], [7, 8, 9]
result = [*a, *b, *c]
print(result) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Python 3.8+ 中赋值表达式的解包
if (matched := re.search(pattern, text)):
start, end = matched.span()
常见应用场景
- 处理CSV数据:
name, age, city = row.split(',') - 配置文件解析:
host, port = config['server'].split(':') - 函数返回值处理:
status, message, data = api_call() - 递归算法:
head, *tail = list_data
注意事项
- 解包时变量数量必须与可迭代对象的元素数量匹配(除非使用
*) *表达式不能用于单独的赋值语句(如*a = [1,2,3]是不允许的)- 解包适用于任何可迭代对象(列表、元组、字符串、字典等)
- 字典解包时默认解包键,如需值或键值对需使用
.values()或.items()
变量解包是Python优雅语法的重要体现,合理使用能让代码更简洁、可读性更强。
9.字典解包的详细示例
让我通过具体例子来说明字典解包的不同情况:
1. 默认解包(解包键)
# 字典默认解包的是键(keys)
person = {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
# 直接解包字典得到的是键
a, b, c = person
print(a, b, c) # name age city
# 循环中默认也是遍历键
for key in person:
print(key) # name, age, city
2. 解包值(使用 .values())
person = {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
# 解包所有的值
a, b, c = person.values()
print(a, b, c) # Alice 30 Beijing
# 循环遍历值
for value in person.values():
print(value) # Alice, 30, Beijing
# 部分解包(结合星号)
first, *rest = person.values()
print(first) # Alice
print(rest) # [30, 'Beijing']
3. 解包键值对(使用 .items())
person = {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
# 解包键值对(得到元组)
a, b, c = person.items()
print(a) # ('name', 'Alice')
print(b) # ('age', 30)
print(c) # ('city', 'Beijing')
# 循环遍历键值对
for key, value in person.items():
print(f"{key}: {value}")
# name: Alice
# age: 30
# city: Beijing
# 解包特定的键值对
items_list = list(person.items())
(k1, v1), (k2, v2), (k3, v3) = person.items()
print(f"{k1}={v1}, {k2}={v2}, {k3}={v3}") # name=Alice, age=30, city=Beijing
4. 实际应用场景
# 场景1:从字典中提取特定值
config = {'host': 'localhost', 'port': 8080, 'debug': True}
host, port, debug = config.values()
print(f"Server running at {host}:{port}, debug={debug}")
# 场景2:同时遍历键和值
scores = {'张三': 85, '李四': 92, '王五': 78}
for name, score in scores.items():
if score >= 90:
print(f"{name} 成绩优秀:{score}分")
# 场景3:转换为其他数据结构
data = {'x': 10, 'y': 20, 'z': 30}
# 转换为键列表
keys = [*data] # 或 list(data)
print(keys) # ['x', 'y', 'z']
# 转换为值列表
values = [*data.values()]
print(values) # [10, 20, 30]
# 转换为键值对列表
items = [*data.items()]
print(items) # [('x', 10), ('y', 20), ('z', 30)]
# 场景4:合并字典(使用**解包)
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged = {**dict1, **dict2}
print(merged) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
# 场景5:函数参数传递
def process_user(name, age, city):
return f"{name} ({age}) from {city}"
user = {'name': 'Bob', 'age': 25, 'city': 'Shanghai'}
# 使用**解包字典作为关键字参数
result = process_user(**user)
print(result) # Bob (25) from Shanghai
5. 对比三种解包方式
data = {'a': 1, 'b': 2, 'c': 3}
# 方式1:解包键
k1, k2, k3 = data
print(f"键: {k1}, {k2}, {k3}") # 键: a, b, c
# 方式2:解包值
v1, v2, v3 = data.values()
print(f"值: {v1}, {v2}, {v3}") # 值: 1, 2, 3
# 方式3:解包键值对
(k1, v1), (k2, v2), (k3, v3) = data.items()
print(f"键值对: {k1}={v1}, {k2}={v2}, {k3}={v3}") # 键值对: a=1, b=2, c=3
6. 注意事项
# 字典是无序的(Python 3.7+ 保持插入顺序,但不要依赖)
# 解包顺序与字典定义顺序一致
# 如果字典大小不确定,使用星号
data = {'x': 10, 'y': 20, 'z': 30, 'w': 40}
first_key, *rest_keys = data
first_val, *rest_vals = data.values()
print(f"第一个键: {first_key}, 其余键: {rest_keys}") # x, ['y', 'z', 'w']
print(f"第一个值: {first_val}, 其余值: {rest_vals}") # 10, [20, 30, 40]
# 变量数量不匹配会报错
# a, b = data # ValueError: too many values to unpack (expected 2)
记住:字典解包的默认行为是处理键,如果需要处理值或键值对,明确使用 .values() 或 .items() 方法。
3.单下划线变量名 _
在Python中,单下划线 _ 有几种重要的用途,让我详细讲解:
1. 作为临时或无关变量(最常用)
# 忽略不需要的值
for _ in range(5):
print("Hello") # 只关心循环次数,不关心索引
# 解包时忽略某些值
person = ('Alice', 30, 'Beijing')
name, _, city = person # 忽略年龄
print(name, city) # Alice Beijing
# 函数返回多个值,只取部分
def get_user_info():
return ('Bob', 25, 'Shanghai', 'Engineer')
name, _, city, _ = get_user_info() # 忽略年龄和职业
print(name, city) # Bob Shanghai
2. 在交互式环境中表示上一个表达式的结果
# 在Python交互式环境(REPL)中
>>> 10 + 20
30
>>> _ # 上一个表达式的结果
30
>>> _ * 2
60
>>> _ # 现在表示60
60
注意:这个特性只在交互式环境(Python shell、Jupyter等)中有效,在脚本文件中无效。
3. 国际化翻译(gettext)
# 用于文本国际化
import gettext
# 通常用 _ 作为翻译函数的别名
_ = gettext.gettext
# 使用 _() 标记需要翻译的文本
print(_("Hello World")) # 根据语言环境显示翻译后的文本
4. 作为私有属性的命名约定
class MyClass:
def __init__(self):
self.public_var = "公开变量"
self._protected_var = "受保护变量" # 约定:不应该直接访问
self.__private_var = "私有变量" # 名称修饰
def _internal_method(self):
"""约定:内部方法,不建议外部调用"""
return "这是内部方法"
def public_method(self):
return self._internal_method()
# 使用
obj = MyClass()
print(obj.public_var) # 可以访问
print(obj._protected_var) # 可以访问,但不建议(约定)
print(obj._internal_method()) # 可以调用,但不建议(约定)
# print(obj.__private_var) # 错误:不能直接访问
命名约定总结:
var:公共属性/方法_var:内部/受保护(约定,不要直接访问)__var:私有(名称修饰,更难访问)__var__:魔术方法(Python定义的特殊方法)
__var__这种命名方式有特殊的语法含义
__name__ # 特殊的属性或方法
__init__ # 构造方法
__str__ # 字符串表示
__len__ # 长度计算
重要:这种命名方式是Python语言规范的一部分,用户不应该创造新的 xxx 变量,只应使用Python预定义的
5. 实际应用示例
示例1:处理数据时忽略不需要的字段
# 解析CSV数据
data = [
('2024-01-01', 'Alice', 95, 'A', '完成'),
('2024-01-02', 'Bob', 87, 'B', '完成'),
('2024-01-03', 'Charlie', 92, 'A', '完成'),
]
# 只关心姓名和分数,忽略日期、等级、状态
for _, name, score, _, _ in data:
print(f"{name}: {score}分")
# Alice: 95分
# Bob: 87分
# Charlie: 92分
示例2:遍历字典的键或值
# 只关心值,不关心键
person = {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
for _, value in person.items():
print(value)
# Alice
# 30
# Beijing
# 只关心键
for key, _ in person.items():
print(key)
# name
# age
# city
示例3:循环中的索引无关性
# 执行5次某个操作,不需要索引
for _ in range(5):
print("执行重要操作...")
# 创建固定长度的列表
result = [0 for _ in range(10)]
print(result) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
示例4:解包中的忽略
# 忽略第一个和最后一个
data = [1, 2, 3, 4, 5]
_, *middle, _ = data
print(middle) # [2, 3, 4]
# 只取第一个和最后一个
first, *_, last = data
print(first, last) # 1 5
# 多个忽略
values = (10, 20, 30, 40, 50)
a, _, c, _, e = values
print(a, c, e) # 10 30 50
6. 特殊用法
# 作为变量名使用(但不推荐)
_ = 100
print(_) # 100
_ = _ + 50
print(_) # 150
# 在try-except中忽略异常
try:
risky_operation()
except ValueError as _: # 捕获但不使用异常对象
print("发生值错误")
# 或者完全忽略异常类型
try:
x = 1 / 0
except: # 不推荐,会捕获所有异常
pass
7. 注意事项
# 1. _ 是一个合法的变量名,但会被linter警告
_ = "hello" # 可以但通常不推荐
# 2. 在交互式环境中会覆盖特殊含义
>>> 10 + 20
30
>>> _ = 100 # 覆盖了特殊含义
>>> _ + 10
110 # 现在 _ 是100,不是30
# 3. 在循环中使用 _ 表示变量真的用不到
for _ in range(3): # 清晰表达索引不重要
do_something()
# 4. 不要滥用 _,只在真正不需要变量时使用
# 不好的例子
name, _, _ = ("Alice", 30, "Beijing") # 应该用 name, age, city
总结
| 使用场景 | 含义 | 示例 |
|---|---|---|
| 临时/无关变量 | "我不需要这个值" | for _ in range(10) |
| 交互式环境 | 上一个表达式的结果 | _ 在REPL中 |
| 国际化翻译 | gettext别名 | _("Hello") |
| 命名约定 | 内部/受保护成员 | self._internal |
核心原则:使用 _ 作为变量名是为了向代码阅读者传达"这个值我不会用到"的意图,让代码更清晰。
4. 类型注解
在 Python 中为变量注明类型,使用的是“类型注解”(Type Annotations)语法。这是一种从 Python 3.5 开始引入的特性,主要用于提高代码的可读性、帮助集成开发环境(IDE)提供更好的代码提示,并配合 mypy 等静态类型检查工具在运行前发现潜在的类型错误。
✍️ 基本语法
为变量添加类型注解的基本语法是 变量名: 类型 = 值。
# 基础类型注解
name: str = "Alice"
age: int = 30
height: float = 1.75
is_active: bool = True
📦 容器类型注解
对于列表(list)、字典(dict)、元组(tuple)等容器类型,可以进一步指定其内部元素的类型。
Python 3.9 及以上版本
可以直接使用内置的小写类型,语法更简洁。
# 元素为整数的列表
numbers: list[int] = [1, 2, 3]
# 键为字符串,值为整数的字典
scores: dict[str, int] = {"math": 90, "english": 85}
# 固定类型的元组
coordinate: tuple[float, float] = (10.5, 20.0)
Python 3.8 及以下版本
需要从 typing 模块中导入对应的大写类型。
from typing import List, Dict, Tuple
numbers: List[int] = [1, 2, 3]
scores: Dict[str, int] = {"math": 90, "english": 85}
coordinate: Tuple[float, float] = (10.5, 20.0)
🤔 可选类型
当一个变量可能为特定类型,也可能为 None 时,可以使用可选类型。
Python 3.10 及以上版本
使用 | 符号连接类型,非常直观。
connection: str | None = None
Python 3.9 及以下版本
需要从 typing 模块导入 Optional。
from typing import Optional
connection: Optional[str] = None
💡 核心要点
- 非强制性:Python 的类型注解是“提示性”的,解释器在运行时会忽略它们,不会强制进行类型检查。因此,
age: int = "thirty"这样的代码不会引发运行时错误,但mypy等工具会报错。 - 主要价值:其核心价值在于代码自文档化、增强 IDE 智能提示以及通过静态检查提前发现 Bug。
- 使用场景:通常在变量类型无法从赋值中直接推断,或者为了明确函数参数/返回值的意图时使用。
在 Python 的类型系统完善之前,Sphinx 风格的文档字符串(Docstring) 确实是业界标准的做法,用来在文档中说明参数类型。
这种方式通常被称为 Napoleon 风格(因为 Sphinx 的 Napoleon 扩展可以解析 Google 和 NumPy 风格的文档)或者直接称为 reStructuredText (reST) 风格。
虽然现在的 IDE 和类型检查器(如 mypy)更推荐使用 Python 3.5+ 引入的类型注解(Type Hints),但 Sphinx 风格的文档在很多老牌项目或注重 API 文档生成的项目中依然非常流行。
📜 Sphinx (reST) 风格示例
这种写法将类型信息写在函数的文档字符串中,使用 :type 和 :rtype 标签。
def fetch_user(user_id, timeout):
"""
根据 ID 获取用户信息。
:param user_id: 用户的唯一标识符
:type user_id: int
:param timeout: 请求超时时间(秒)
:type timeout: float
:return: 包含用户信息的字典,如果未找到则返回 None
:rtype: dict or None
:raises ValueError: 如果 user_id 小于 0
"""
if user_id < 0:
raise ValueError("user_id 不能为负数")
# 模拟逻辑...
return {"id": user_id, "name": "Alice"}
⚖️ 两种方式的对比
为了帮你更好地决定在什么场景下使用哪种方式,我做了一个对比表:
| 特性 | 现代类型注解 (Type Hints) | Sphinx 风格文档 (Docstring) |
|---|---|---|
| 语法位置 | 函数定义行内 (def func(a: int):) | 函数内部的文档字符串中 |
| IDE 支持 | 极佳 (PyCharm/VS Code 直接识别并提示) | 一般 (部分 IDE 需插件支持才能识别类型) |
| 静态检查 | 原生支持 (mypy, pyright 等工具的标准) | 不支持 (工具无法通过 Docstring 检查类型错误) |
| 文档生成 | 需配合 autodoc 插件提取注解 | 原生支持 (Sphinx 生成文档的标准格式) |
| 可读性 | 代码更简洁,类型与逻辑分离 | 文档内容集中,但函数签名较长时显得杂乱 |
| 适用场景 | 现代 Python 项目 (3.5+),注重代码质量 | 需要生成详细 API 文档的老项目,或注重文档描述 |
🤝 最佳实践:混合使用
在实际的大型项目中,我们往往不需要二选一,而是可以结合两者的优点:
- 使用 类型注解 给 IDE 和静态检查工具提供信息,确保代码质量。
- 使用 Sphinx 风格文档 来补充详细的参数说明、业务逻辑描述和异常抛出说明,用于生成漂亮的 HTML 文档。
混合写法示例:
from typing import Optional, Dict
def fetch_user(user_id: int, timeout: float = 5.0) -> Optional[Dict[str, str]]:
"""
根据 ID 获取用户信息(这里是 Sphinx 风格的详细描述)。
这个函数会连接数据库查询用户,如果超时或用户不存在会返回 None。
:param user_id: 用户的唯一标识符,必须为正整数。
:param timeout: 数据库查询的超时时间,单位是秒。
:return: 如果找到用户,返回包含 'id' 和 'name' 的字典;否则返回 None。
:raises ValueError: 当 user_id 小于 0 时抛出。
"""
if user_id < 0:
raise ValueError("user_id 不能为负数")
# 模拟逻辑...
return {"id": str(user_id), "name": "Alice"}
💡 总结
- 如果你关注代码运行时的健壮性和开发体验(自动补全、报错),类型注解是必须的。
- 如果你关注对外发布的 API 文档的丰富程度,Sphinx 风格依然是文档生成的王者。
现在的趋势是:类型注解负责“类型”,文档字符串负责“解释”。
5.python变量命名原则
在 Python 中,变量命名不仅仅是给数据起个代号,更是代码可读性和维护性的基石。Python 社区非常推崇“代码可读性”,因此有一套广为接受的命名规范(主要基于 PEP 8 标准)。
我们可以将命名原则分为**“硬性规则”(必须遵守,否则报错)和“软性规范”**(建议遵守,为了代码优雅)两部分。
🚦 硬性规则:不可逾越的红线
这些是 Python 解释器的语法要求,违反会导致 SyntaxError:
- 字符限制:只能包含字母(a-z, A-Z)、数字(0-9)和下划线(_)。
- 开头限制:不能以数字开头。
- ✅
user1(正确) - ❌
1user(错误)
- ✅
- 关键字禁用的:不能使用 Python 的保留关键字(如
if,for,class,def,import等)。- 你可以通过
import keyword; print(keyword.kwlist)查看所有关键字。
- 你可以通过
- 区分大小写:
Age、age和AGE是三个完全不同的变量。 - 禁止特殊符号:不能包含空格、连字符(-)、@、$ 等符号。
🎨 软性规范:Python 的“优雅之道”
遵循 PEP 8 规范,能让你的代码看起来更专业、更易读。
1. 核心命名风格
Python 中主要使用 蛇形命名法 和 大驼峰命名法,具体取决于变量的用途:
| 类型 | 命名风格 | 示例 | 说明 |
|---|---|---|---|
| 普通变量 | 蛇形命名法 (snake_case) | user_name, total_count | 全小写,单词间用下划线分隔。 |
| 常量 | 全大写下划线 (CONSTANT_CASE) | MAX_SIZE, PI | 所有字母大写,单词间用下划线。 |
| 类名 | 大驼峰命名法 (PascalCase) | Student, UserProfile | 每个单词首字母大写。 |
2. 语义化原则(见名知意)
变量名应该清晰地描述它存储的数据。
- 拒绝模糊缩写,描述性要强:
- ❌
lst = [](什么列表?) - ✅
user_list = [] - ❌
n = 10(什么数量?) - ✅
max_retries = 10
- ❌
- 匹配类型:
- 布尔值命名:
- 通常使用
is_,has_,can_,should_作为前缀,使其读起来像一个判断句。 - ✅
is_active = True - ✅
has_permission = False
- 通常使用
- 集合类型命名:
- 列表、集合等容器建议使用复数名词。
- ✅
users = [...](比user_list更简洁) - ✅
products = {...}
- 布尔值命名:
3. 特殊下划线用法
_single_leading_underscore(单前导下划线):- 表示该变量是**“内部使用”**的(类似私有变量),虽然 Python 解释器不强制禁止访问,但暗示外部代码不应直接使用它。
double_leading_trailing__(双前后下划线):- 这是 Python 的魔术方法(Magic Methods),如
__init__,__str__。不要自己发明这种命名方式,除非重写内置方法。
- 这是 Python 的魔术方法(Magic Methods),如
single_trailing_underscore_(单尾随下划线):- 用于避免与 Python 关键字冲突。例如:
class_ = "Math"(因为class是关键字)。
- 用于避免与 Python 关键字冲突。例如:
🚫 常见反模式(避坑指南)
- 使用拼音或中文:
- 虽然 Python 3 支持中文变量名(如
年龄 = 18),但在团队协作和国际化中是极度不推荐的,容易导致编码问题和阅读障碍。
- 虽然 Python 3 支持中文变量名(如
- 误导性命名:
- 不要用
list作为变量名,因为它遮蔽了 Python 的内置类型list。同理避免使用str,dict,id等。
- 不要用
- 过度缩写:
cust_nm不如customer_name清晰。除非缩写是业界通用的(如id,url,html)。
📌 总结
好的变量命名应该像一句通顺的英语句子。当你阅读代码时,应该能直接理解其意图,而不需要去猜测 x 或 temp 到底代表什么。
示例对比:
# ❌ 糟糕的命名
d = 7 # 是什么的7?
l = [] # 什么列表?
f = True # 什么状态?
# ✅ 优雅的命名
week_days = 7
pending_tasks = []
is_system_ready = True
6.注释的核心原则
在 Python 中,编写高质量的注释和文档是专业开发的重要一环。这不仅关乎代码能否运行,更关乎代码能否被他人(以及未来的你)轻松理解和维护。
下面我将为你讲解 Python 注释的核心原则,并结合一个完整的函数示例,说明如何编写规范的接口文档注释(Docstring)。
📜 Python 注释的核心原则
编写注释时,请牢记以下黄金法则,它们能让你的代码更具可读性和可维护性:
-
解释“为什么”,而非“做什么” (Why, not What) 这是最重要的原则。好的代码本身就能清晰地表达它在“做什么”。注释的价值在于揭示代码背后的意图、业务逻辑、设计权衡或特殊原因。
- 差的注释 (复述代码):
# 将 count 变量加 1 count = count + 1 - 好的注释 (解释原因):
# 补偿数组索引从0开始,而用户期望的序号从1开始 user_facing_index = internal_index + 1
- 差的注释 (复述代码):
-
保持注释与代码同步 过时的注释比没有注释更糟糕,因为它会提供错误信息,误导阅读者。当你修改代码逻辑时,务必同步更新相关的注释。
-
清晰、准确、简洁 注释的目的是沟通。使用完整、清晰的句子,避免模糊不清或过于冗长的描述。
-
避免无意义的注释 不要注释掉废弃的代码(使用 Git 等版本控制工具来管理历史代码)。也不要为显而易见的代码添加注释。
📝 函数接口文档注释 (Docstring) 详解
Docstring 是一种特殊的注释,它使用三引号 """ 包裹,并且必须放在模块、函数、类或方法定义后的第一行。它会被 Python 存储在对象的 __doc__ 属性中,可以被 help() 函数调用,也是 Sphinx 等工具自动生成 API 文档的基础。
业界有多种 Docstring 风格规范,如 Google 风格、NumPy 风格等。下面以最流行的 Google 风格为例,展示一个规范的函数接口文档。
Google 风格更像是在写文章,利用缩进来区分层级,阅读体验通常更好。
完整示例:用户折扣计算函数
from typing import List, Dict
def calculate_final_price(user_id: int, cart_items: List[Dict], coupon_code: str = None) -> float:
"""
计算用户购物车的最终结算价格。
该函数会根据用户的会员等级和应用的有效优惠券来计算最终折扣价。
注意:此函数不会修改数据库中的用户积分。
Args:
user_id (int): 用户的唯一标识符。
cart_items (List[Dict]): 购物车中的商品列表。每个商品是一个字典,
必须包含 'price' (float) 和 'quantity' (int) 键。
coupon_code (str, optional): 优惠券代码。默认为 None,表示不使用优惠券。
Returns:
float: 经过所有折扣计算后的最终价格,保留两位小数。
Raises:
ValueError: 如果 cart_items 为空列表,或商品字典缺少必需的键。
KeyError: 如果提供的 coupon_code 无效或已过期。
Example:
>>> items = [{'price': 100.0, 'quantity': 2}]
>>> calculate_final_price(123, items, 'SUMMER20')
160.0
"""
# 1. 验证购物车是否为空
if not cart_items:
raise ValueError("购物车不能为空。")
# 2. 计算商品总价
total_price = sum(item['price'] * item['quantity'] for item in cart_items)
# 3. 获取用户会员折扣 (假设 VIP 用户享受 9 折)
# NOTE: 这里简化了用户等级查询逻辑
user_level = get_user_level(user_id)
if user_level == "VIP":
total_price *= 0.9
# 4. 应用优惠券折扣
if coupon_code:
# 如果优惠券无效,会抛出 KeyError
discount_rate = validate_and_apply_coupon(coupon_code)
total_price *= (1 - discount_rate)
# 5. 返回保留两位小数的最终价格
return round(total_price, 2)
# --- 以下为模拟的辅助函数,仅用于示例 ---
def get_user_level(uid: int) -> str:
return "VIP"
def validate_and_apply_coupon(code: str) -> float:
if code == 'SUMMER20':
return 0.2
raise KeyError(f"优惠券 '{code}' 无效。")
结构拆解
这个 Docstring 清晰地展示了函数接口的所有关键信息:
- 摘要 (Summary): 第一行简明扼要地概括了函数的功能。
- 详细描述 (Description): 空一行后,提供更详细的说明,包括业务规则、副作用等。
- 参数 (Args): 列出每个参数的名称、类型和说明。对于可选参数,会注明默认值。
- 返回值 (Returns): 描述返回值的类型和含义。
- 异常 (Raises): 说明函数可能抛出的异常类型及其触发条件,这对调用者至关重要。
- 示例 (Example): 提供一个或多个简单的使用示例,能让使用者快速上手。
遵循这样的原则和规范,你的代码将变得非常专业和友好。
“指引性注释
**“指引性注释”**确实是 Python(以及很多编程语言)中一种非常重要且高级的注释技巧。
虽然它不是 Python 语法上的一个新分类(它依然使用 #),但在代码可读性和维护性的层面上,它代表了一种更成熟的编程思维。
简单来说,指引性注释的核心作用是**“降低代码认知成本”**,它像路标一样告诉阅读者:“这段代码在做什么”或者“为什么要这么做”。
我们可以从以下三个维度来深入理解“指引性注释”:
1. 概括性指引(代码导读)
这种注释通常写在一段复杂逻辑或长代码块的上方,用来概括接下来的代码功能。
- 作用:让阅读者不需要逐行分析代码细节,就能快速掌握代码的意图。
- 对比:
- ❌ 复述代码(坏注释):
(这种注释只是把代码翻译成了中文,毫无意义。)# 初始化 client 对象 client = ServiceClient(token=token) client.ready() - ✅ 指引性注释(好注释):
(它概括了这三行代码的整体目标,而不是机械地翻译每一行。)# 初始化访问服务的 client 对象,并检查服务是否就绪 token = token_service.get_token() service_client = ServiceClient(token=token) service_client.ready()
- ❌ 复述代码(坏注释):
2. 解释性指引(解释“为什么”)
这是指引性注释中最有价值的一种。它解释代码背后的业务逻辑、设计权衡或特殊原因。
- 作用:防止后来者因为看不懂代码的“怪异”写法而误删或误改。
- 示例:
如果没有这行指引,你可能会觉得# 使用 strip() 去除首尾空格,防止用户误输入空格导致后端校验失败 username = input_string.strip()strip()是多余的,甚至把它删掉。
3. 任务标记指引(TODO / FIXME)
这是一种特殊的指引,用于标记未来的工作或已知问题。IDE 通常会自动高亮这些标签。
# TODO::指引未来需要添加的功能。# FIXME::指引这里有 Bug 或糟糕的代码,需要修复。# NOTE::指引这里有一个重要的注意事项。
def validate_phone(number):
# TODO: 未来需要添加对国际手机号格式的支持
if len(number) != 11:
return False
return True
总结:什么是好的“指引性注释”?
为了让你更直观地掌握,我整理了一个对比表:
| 维度 | ❌ 糟糕的注释(复述代码) | ✅ 好的指引性注释 |
|---|---|---|
| 关注点 | 关注代码做了什么 (What) | 关注代码为什么这么做 (Why) 或 整体意图 |
| 示例 | # 将 x 加 1 x = x + 1 | # 补偿数组索引从 0 开始导致的偏移 x = x + 1 |
| 效果 | 增加阅读噪音,冗余 | 降低理解门槛,提供上下文 |
| 位置 | 往往跟在行尾 | 通常在代码块的上方 |
所以,当你写注释时,不妨停下来想一想:“如果我不写这行注释,别人能看懂吗?如果看不懂,我是指引了方向,还是只是在翻译代码?” 这就是指引性注释的精髓。
7.Python 编程建议:变量管理与代码组织
这些是非常实用的编程最佳实践,让我详细讲解每条建议:
1. 保持变量的一致性
# ❌ 不一致的命名风格
userName = "张三" # 驼峰
user_age = 25 # 蛇形
UserAddress = "北京" # 帕斯卡
# ✅ 保持一致的命名风格(推荐 snake_case)
user_name = "张三"
user_age = 25
user_address = "北京"
# ❌ 不一致的数据类型使用
items = [1, 2, 3]
items = "1,2,3" # 改变了类型,容易出错
# ✅ 保持变量类型一致
items_list = [1, 2, 3]
items_str = "1,2,3"
# ❌ 混用同义词
def get_user_info():
user_data = fetch_data()
return user_info # data 和 info 混用
# ✅ 统一术语
def get_user_info():
user_info = fetch_data()
return user_info
2. 变量定义尽量靠近使用
# ❌ 不好:变量定义远离使用位置
def process_order(order_id):
# 一大堆变量在开头定义
user = None
items = []
total = 0
discount = 0
tax = 0
shipping = 0
final_total = 0
# ... 100行代码 ...
user = get_user(order_id) # 到这里才使用
items = get_items(order_id)
# 忘记了某些变量,容易出错
# ✅ 好:在使用前才定义
def process_order(order_id):
user = get_user(order_id) # 立即使用
if not user:
return None
items = get_items(order_id) # 使用时定义
if not items:
return None
total = calculate_total(items)
discount = calculate_discount(user, total)
tax = calculate_tax(total - discount)
return total - discount + tax
3. 定义临时变量提升可读性
# ❌ 难以阅读:表达式过于复杂
def calculate_score(student):
return (sum(student.scores) / len(student.scores)) * 0.6 + (student.attendance / student.total_classes) * 0.4
# ✅ 使用临时变量提升可读性
def calculate_score(student):
# 计算成绩部分(60%权重)
average_score = sum(student.scores) / len(student.scores)
score_part = average_score * 0.6
# 计算出勤部分(40%权重)
attendance_rate = student.attendance / student.total_classes
attendance_part = attendance_rate * 0.4
return score_part + attendance_part
# 更多示例
# ❌ 复杂条件判断
if user.is_active and user.age >= 18 and user.has_permission and not user.is_banned:
grant_access()
# ✅ 使用临时变量
is_adult = user.age >= 18
is_eligible = user.is_active and is_adult and user.has_permission
is_not_banned = not user.is_banned
if is_eligible and is_not_banned:
grant_access()
4. 同一作用域内不要有太多变量
# ❌ 变量过多(超过7-10个)
def process_data():
name = "张三"
age = 25
city = "北京"
address = "朝阳区"
phone = "13800138000"
email = "zhang@example.com"
company = "ABC公司"
position = "工程师"
salary = 15000
department = "技术部"
hire_date = "2024-01-01"
# ... 继续更多变量
# ✅ 使用数据结构组织相关变量
def process_data():
# 使用字典分组相关数据
personal_info = {
"name": "张三",
"age": 25,
"city": "北京",
"address": "朝阳区"
}
contact_info = {
"phone": "13800138000",
"email": "zhang@example.com"
}
work_info = {
"company": "ABC公司",
"position": "工程师",
"salary": 15000,
"department": "技术部",
"hire_date": "2024-01-01"
}
# 或者拆分成多个小函数
process_personal(personal_info)
process_contact(contact_info)
process_work(work_info)
# ✅ 使用类封装
class Employee:
def __init__(self, name, age, city, address, phone, email,
company, position, salary, department, hire_date):
self.personal = PersonalInfo(name, age, city, address)
self.contact = ContactInfo(phone, email)
self.work = WorkInfo(company, position, salary, department, hire_date)
5. 能不定义变量就别定义
# ❌ 不必要的变量
def get_full_name(first, last):
full_name = first + " " + last # 只使用一次的变量
return full_name
# ✅ 直接返回表达式
def get_full_name(first, last):
return first + " " + last
# ❌ 过度使用临时变量
def is_positive(number):
result = number > 0
return result
# ✅ 直接返回
def is_positive(number):
return number > 0
# ❌ 中间变量只用一次
def calculate(price, quantity):
subtotal = price * quantity
tax = subtotal * 0.1
total = subtotal + tax
return total
# ✅ 合并计算(但注意可读性)
def calculate(price, quantity):
return price * quantity * 1.1
# ⚠️ 权衡:需要在可读性和简洁性之间平衡
# 如果逻辑复杂,临时变量反而是好的选择
6. 不要使用 locals()
# ❌ 危险:使用 locals()
def create_user(name, age, email):
# 不要这样做!
user = locals() # 包含了所有局部变量
return user
# 问题:
# 1. 包含不需要的变量(self、函数参数等)
# 2. 调试困难
# 3. 容易意外暴露敏感信息
# 4. 代码难以维护
# ✅ 明确指定需要的字段
def create_user(name, age, email):
return {
"name": name,
"age": age,
"email": email
}
# 或者使用数据类
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
def create_user(name, age, email):
return User(name=name, age=age, email=email)
# ❌ 另一个危险用法
def process_data(**kwargs):
# 不要将 locals() 传递给其他函数
result = some_function(locals()) # 传递了所有局部变量
return result
7. 空行也是一种注释
# ❌ 没有空行,代码挤在一起
def process_order(order):
user = get_user(order.user_id)
items = get_items(order.id)
total = calculate_total(items)
discount = calculate_discount(user)
tax = calculate_tax(total)
final_total = total - discount + tax
if final_total < 0:
final_total = 0
order.final_total = final_total
save_order(order)
send_notification(user)
return order
# ✅ 使用空行分隔逻辑块
def process_order(order):
# 获取用户和商品信息
user = get_user(order.user_id)
items = get_items(order.id)
# 计算价格
total = calculate_total(items)
discount = calculate_discount(user)
tax = calculate_tax(total)
final_total = total - discount + tax
# 确保价格不为负数
if final_total < 0:
final_total = 0
# 保存并通知
order.final_total = final_total
save_order(order)
send_notification(user)
return order
# 空行用于分隔:
# - 不同逻辑块
# - 函数定义之间(两个空行)
# - 类定义之间(两个空行)
# - 类内方法之间(一个空行)
8. 先写注释后写代码
# ❌ 先写代码,后补注释
def calculate_interest(principal, rate, years):
r = rate / 100
a = principal * (1 + r) ** years
return a
# 注释:这个函数计算复利
# ✅ 先写注释,明确意图
def calculate_interest(principal, rate, years):
"""
计算复利
使用公式:A = P(1 + r)^n
其中:
A = 最终金额
P = 本金
r = 年利率(转换为小数)
n = 年数
"""
# 将百分比转换为小数
rate_decimal = rate / 100
# 应用复利公式
final_amount = principal * (1 + rate_decimal) ** years
return final_amount
# 更详细的示例
def validate_user_input(username, email, age):
# 验证用户名:3-20个字符,只能包含字母、数字和下划线
if not (3 <= len(username) <= 20):
return False
if not username.replace('_', '').isalnum():
return False
# 验证邮箱:包含@符号和域名
if '@' not in email or '.' not in email.split('@')[-1]:
return False
# 验证年龄:18-120岁之间
if not (18 <= age <= 120):
return False
return True
综合示例:应用所有建议
"""
用户订单处理模块
"""
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class OrderItem:
"""订单商品项"""
product_id: int
quantity: int
price: float
class OrderProcessor:
"""订单处理器"""
def process_order(self, order_id: int, user_id: int) -> Optional[dict]:
"""
处理订单并返回处理结果
流程:
1. 验证订单和用户
2. 计算订单总额
3. 应用折扣
4. 保存处理结果
"""
# 获取订单数据
order = self._get_order(order_id)
if not order:
return None
# 验证用户权限
user = self._get_user(user_id)
if not self._has_permission(user):
return None
# 计算总额
subtotal = self._calculate_subtotal(order.items)
discount = self._calculate_discount(user, subtotal)
tax = self._calculate_tax(subtotal - discount)
# 计算最终价格
final_total = subtotal - discount + tax
if final_total < 0:
final_total = 0 # 业务规则:订单金额不能为负数
# 保存并返回结果
return self._save_order_result(order_id, final_total)
def _calculate_subtotal(self, items: List[OrderItem]) -> float:
"""计算小计金额"""
# 使用临时变量提升可读性
total_quantity = sum(item.quantity for item in items)
total_price = sum(item.price * item.quantity for item in items)
return total_price
def _calculate_discount(self, user: dict, amount: float) -> float:
# 先写注释,明确折扣规则
# VIP用户享受20%折扣
# 普通用户消费满1000享受10%折扣
if user.get('vip'):
return amount * 0.2
if amount >= 1000:
return amount * 0.1
return 0.0
快速检查清单
# 代码审查时可以问自己:
✅ 变量命名是否一致?
✅ 变量定义是否靠近使用位置?
✅ 复杂表达式是否使用了临时变量?
✅ 函数内变量是否过多?(< 7-10个)
✅ 是否定义了不必要的变量?
✅ 是否避免了 locals()?
✅ 是否使用空行分隔逻辑块?
✅ 是否先写注释再写代码?
这些建议的核心目标是:写出清晰、易懂、易维护的代码。记住:代码是写给人看的,只是顺便让机器执行。