第八篇:变量与常量——动态类型、引用模型与数值运算
前言
在深入 Python 编程的过程中,理解变量和常量的本质至关重要。与其他静态类型语言不同,Python 的变量机制有独特之处。本文将彻底讲解变量命名规则、赋值语句的本质、动态语言的类型推断机制、变量在内存中的引用模型,以及整数除法 / 与 // 和取模 % 运算的区别与用法。
变量的本质:命名而非容器
什么是变量?
在很多教学资料中,变量被描述为"盒子"或"容器"。这个比喻是错误的! 正确的理解是:
# ❌ 错误比喻:变量是一个盒子,里面装了数字 42
var = 42 # 把 42 放进 var 这个盒子
# ✅ 正确理解:变量是一个标签/别名,贴在某个对象上
var = 42 # 给 int(42) 这个对象贴上"var"的标签
Python 变量声明的本质
# Python 没有传统意义上的"变量声明"
# 赋值语句 = 名称绑定操作
x = 42 # 创建 int 对象 42,让 x 指向它
print(x) # 42
print(type(x)) # <class 'int'>
y = x # y 也指向同一个 int 对象 42
print(y) # 42
x = 100 # 创建新的 int 对象 100,让 x 重新指向它
# y 仍然指向原来的 42
print(x) # 100
print(y) # 42 — y 不受影响!
这与 Java、C++ 等静态语言完全不同。Java 先在栈上分配空间存储值,而 Python 创建一个对象,x 成为它的名字。
id() 函数验证内存模型
a = 42
b = a
c = 42
print(id(a)) # 例如 140234867153584
print(id(b)) # 同上 — a 和 b 指向同一对象
print(id(c)) # 同上 — Python 优化了相同小整数的缓存
# 证明它们指向同一个对象
print(a is b) # True
print(a is c) # True
# 重新赋值后
b = 100
print(id(a)) # 仍然是原来那个对象的 id
print(id(b)) # 新对象的 id,不同
print(a is b) # False — 现在指向不同对象
关键概念:id() 返回对象的唯一标识(通常是内存地址)
变量命名规则
合法命名规则
| 规则 | ✅ 正确 | ❌ 错误 |
|---|---|---|
| 只能含字母、数字、下划线 | user_name |
user-name |
| 不能以数字开头 | name2 |
2name |
| 不能使用关键字 | data |
class, if, for |
| 区分大小写 | Name ≠ name |
— |
命名约定(PEP 8)
# 变量和函数名:snake_case(小写 + 下划线)
user_count = 42
calculate_total()
is_active = True
# 类名:CamelCase(每个单词首字母大写)
class UserProfile:
pass
# 常量:全大写 + 下划线
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT = 30
API_KEY = "abc123"
# 私有属性/方法:前缀单下划线
_internal_data = "内部使用"
def _helper_function():
pass
# 特殊属性:前后双下划线(魔术方法)
__init__
__str__
__len__
避免的命名陷阱
# ❌ 不要用内置函数名作为变量名
list = [1, 2, 3] # 覆盖了内置的 list()
str = "hello" # 覆盖了内置的 str()
type = int # 覆盖了内置的 type()
# 修复后可以正常使用
del list
my_list = [1, 2, 3] # 正确做法
# ❌ 不要用容易混淆的名称
l = 1 # 小写字母 l 看起来像数字 1
O = 0 # 大写字母 O 看起来像零
I = 1 # 大写字母 I 看起来像数字 1
# ✅ 使用清晰有意义的名称
length = 1
zero = 0
one = 1
Python 的关键字
import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
# 'break', 'class', 'continue', 'def', 'del', 'elif', 'else',
# 'except', 'finally', 'for', 'from', 'global', 'if', 'import',
# 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass',
# 'raise', 'return', 'try', 'while', 'with', 'yield']
# 检查一个名称是否可用
def is_valid_identifier(name):
return name.isidentifier() and not keyword.iskeyword(name)
print(is_valid_identifier("user")) # True
print(is_valid_identifier("for")) # False — 关键字
print(is_valid_identifier("123abc")) # False — 以数字开头
动态类型 vs 静态类型
核心区别
# Python 是动态类型语言
x = 42 # x 是 int
x = "hello" # x 变成 str
x = [1, 2, 3] # x 变成 list
x = True # x 变成 bool
# 同一变量可以随时变类型 —— 这是动态类型的特征
print(type(x)) # <class 'bool'>
对比 Java(静态类型):
// Java 编译时就要确定类型,之后不能改变
int x = 42;
x = "hello"; // ❌ 编译错误!Cannot assign String to int
动态类型的优势
# 优势一:灵活多态
def process(data):
if isinstance(data, list):
return sum(data)
elif isinstance(data, str):
return len(data)
elif isinstance(data, dict):
return len(data.keys())
return 0
process([1, 2, 3]) # 6
process("Hello") # 5
process({"a": 1}) # 1
动态类型的代价
# 代价一:运行时错误(编译期无法发现)
def divide(a, b):
return a / b
divide(10, 2) # 5.0 — 正常工作
divide("10", 2) # TypeError! — 运行时才发现
赋值的高级用法
多重赋值
# 同时给多个变量赋值
x, y, z = 1, 2, 3
print(x, y, z) # 1 2 3
# 交换两个变量的值(最优雅的方式)
a, b = 10, 20
a, b = b, a
print(a, b) # 20 10
解包赋值
# 列表解包
first, second, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(second) # 2
print(rest) # [3, 4, 5]
# 嵌套解包
coords = [[1, 2], [3, 4]]
[x1, y1], [x2, y2] = coords
print(x1, y1) # 1 2
print(x2, y2) # 3 4
链式赋值
# 多个变量指向同一个对象
x = y = z = 0
print(x, y, z) # 0 0 0
# 注意:可变对象需要小心!
a = []
b = a
a.append(1)
print(b) # [1] — b 也受影响了
# 如果要独立副本,需要显式复制
a = []
b = a.copy() # 或使用 list(a)
a.append(1)
print(b) # [] — 互不影响
Python 的整数除法和取模
/ — 真除法(总是返回浮点数)
print(10 / 3) # 3.3333333333333335
print(10 / 2) # 5.0 — 即使整除也是 float
print(-10 / 3) # -3.3333333333333335
print(10 / 0) # ZeroDivisionError!
// — 地板除法(向下取整)
print(10 // 3) # 3
print(10 // 2) # 5
print(-10 // 3) # -4 — 向负无穷方向舍入
print(10.5 // 3) # 3.0
# floor(-3.333) = -4 — 向负无穷舍入
# 而不是 trunc(-3.333) = -3 — 向零截断
% — 取模(求余数)
# % 运算符返回除法的余数
# 关系式:a = (a // b) * b + (a % b)
print(10 % 3) # 1 — 10 = 3×3 + 1
print(10 % 2) # 0
print(-10 % 3) # 2
# ⚠️ Python 的 % 保证结果与除数同号
# 公式保证:(a // b) * b + (a % b) == a
实际应用场景
# 场景一:时间转换
seconds = 3665
hours = seconds // 3600
remaining_seconds = seconds % 3600
minutes = remaining_seconds // 60
secs = remaining_seconds % 60
print(f"{hours}h {minutes}m {secs}s")
# 输出:1h 1m 5s
# 场景二:判断奇偶
print(42 % 2 == 0) # True
print(13 % 2 == 0) # False
# 场景三:周期性排列
names = ["Alice", "Bob", "Charlie"]
for i in range(10):
speaker = names[i % len(names)]
print(f"发言人{i+1}: {speaker}")
常量:约定俗成
Python 没有真正的常量(编译期不可修改的值),但有约定俗成的写法:
# 惯例:大写命名表示"不应该被修改"
PI = 3.14159265358979
MAX_CONNECTIONS = 100
VERSION = "3.12.5"
# 但它们本质上仍然可以被修改
PI = 3 # 不会报错,只是违反约定
print(PI) # 3
# 真正不可变的替代方案
from types import SimpleNamespace
config = SimpleNamespace(host="localhost", port=8080)
作用域与命名空间
LEGB 规则
Python 查找变量的顺序:
- L — Local(局部作用域:函数内部)
- E — Enclosing(外层函数作用域)
- G — Global(模块级全局作用域)
- B — Built-in(Python 内置作用域)
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x) # 找到 local,停止查找
inner()
outer() # 输出:local
nonlocal 关键字
def outer():
count = 0
def inner():
nonlocal count # 引用外层函数的变量
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
总结
本文讲解了变量与常量的核心知识:
- 变量的本质 — 变量是对象上的"标签",不是容器
- 引用模型 — 通过 id() 可验证多个变量指向同一对象
- 命名规则 — 遵循 PEP 8 命名约定(snake_case/CamelCase)
- 动态类型 — 变量可随时改变类型,灵活但有运行时风险
- 高级赋值 — 多重赋值、解包、链式赋值
- 除法运算 —
/(真除法)、//(地板除法)、%(取模) - 常量约定 — 大写下划线命名,无编译期保护
- 作用域规则 — LEGB 查找顺序
下一篇文章将深入讲解列表 list 的增删改查、切片、排序等完整操作。
本文是 Python 教程系列的第八篇。
