01-变量与数据类型
变量是程序存储数据的基本方式,理解变量和数据类型是编程的第一步。
本章讲解变量的概念、命名规则和 Python 基本数据类型。
概念铺垫
为什么需要变量?一个真实的存储场景
问题场景: 你在开发一个学生信息管理程序,需要存储每个学生的姓名、年龄、成绩。
不使用变量的困惑:
- 如何在程序中"记住"这些数据?
- 数据处理完后还能找到吗?
- 如何在程序的不同地方使用相同的数据?
使用变量的解决方案:
# 用变量存储学生信息
name = "张三"
age = 18
score = 85.5
# 在程序中多次使用
print(f"学生姓名:{name}") # 输出:张三
print(f"年龄:{age}岁") # 输出:18岁
print(f"成绩:{score}分") # 输出:85.5分
# 数据仍然保留,可以继续使用
average = score / 3 # 计算平均分这就是变量的价值:用一个名字,存储和复用数据。
变量解决了什么问题?
变量的本质是:给数据一个"名字",方便存储和引用。
就像你给文件贴标签,方便以后找到。变量就是程序中的"标签"。
变量的优势:
- 存储数据:程序运行时保存信息
- 复用数据:同一个数据多次使用,不用重复写
- 修改数据:可以随时更新存储的内容
- 传递数据:在不同地方传递和处理数据
数据类型的作用:
数据类型告诉程序"这个变量是什么",就像标签上的说明:
int:整数(如年龄:18)float:浮点数(如成绩:85.5)str:字符串(如姓名:"张三")bool:布尔值(如是否通过:True)
变量的最简用法
变量只需要3步:创建、使用、修改。
# 创建变量(变量名 = 值)
name = "Python"
age = 33
# 使用变量
print(name) # 输出:Python
print(age) # 输出:33
# 修改变量
age = 34
print(age) # 输出:34这就是变量的基本用法。接下来我们详细学习变量的特性。
什么是变量
变量(Variable) 是程序中用于存储数据的"容器"。
┌─────────────────────────────────────────┐
│ 变量示意图 │
├─────────────────────────────────────────┤
│ │
│ 变量名 变量值 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────┐ ┌───────┐ │
│ │ name│────▶│"Alice"│ │
│ └─────┘ └───────┘ │
│ │
│ ┌─────┐ ┌───────┐ │
│ │ age │────▶│ 25 │ │
│ └─────┘ └───────┘ │
│ │
└─────────────────────────────────────────┘L1 理解层:会用
变量的创建和使用
基本语法
┌─────────────────────────────────────────┐
│ 变量赋值语法 │
├─────────────────────────────────────────┤
│ │
│ 变量名 = 值 │
│ │
│ 读作:"将 值 赋给 变量名" │
│ │
└─────────────────────────────────────────┘示例代码
# 创建变量
name = "Alice"
age = 25
height = 1.68
is_student = True
# 使用变量
print(name) # 输出:Alice
print(age) # 输出:25
print(height) # 输出:1.68
print(is_student) # 输出:True变量命名规则
┌─────────────────────────────────────────┐
│ 变量命名规则 │
├─────────────────────────────────────────┤
│ │
│ ✅ 可以做: │
│ • 只能包含字母、数字、下划线 (_) │
│ • 可以以字母或下划线开头 │
│ • 区分大小写(name 和 Name 不同) │
│ │
│ ❌ 不可以: │
│ • 不能以数字开头 │
│ • 不能包含空格或特殊字符 │
│ • 不能使用 Python 关键字 │
│ │
└─────────────────────────────────────────┘# ✅ 正确的命名
name = "Alice"
user_name = "Bob"
_age = 25
# ❌ 错误的命名
# 2name = "David" # 不能以数字开头
# user-name = "Eve" # 不能有连字符
# class = "Math" # 不能使用关键字变量的赋值方式
链式赋值
# 多个变量赋相同的值
a = b = c = 0
print(a, b, c) # 输出:0 0 0多重赋值
# 同时给多个变量赋不同的值
name, age, city = "Alice", 25, "Beijing"
print(name) # 输出:Alice
print(age) # 输出:25
print(city) # 输出:Beijing交换变量值
Python 特性: 无需临时变量,直接交换。
交换变量值:
┌─────────────────────────────────────────────────────────────┐
│ Python 变量交换 │
│ │
│ a, b = b, a │
│ ↑ ↑ │
│ 赋值 元组解包 │
│ │
│ 执行过程: │
│ 1. 右侧 b, a 先打包为元组 (b, a) │
│ 2. 元组解包赋值给左侧 a, b │
│ │
│ 示例: │
│ a = 10, b = 20 │
│ a, b = b, a │
│ 结果:a = 20, b = 10 │
└─────────────────────────────────────────────────────────────┘最简示例
a = 10
b = 20
a, b = b, a # 一行交换
print(a, b) # 输出:20 10关键代码解释
| 代码 | 含义 | 执行顺序 |
|---|---|---|
b, a | 右侧表达式 | 先打包为元组 (20, 10) |
a, b = | 左侧赋值 | 元组解包赋值 |
数据类型
Python 常见数据类型
┌─────────────────────────────────────────┐
│ Python 常见数据类型 │
├─────────────────────────────────────────┤
│ │
│ 数值类型: │
│ • int(整数):1, 2, 100, -5 │
│ • float(浮点数):3.14, -0.5 │
│ │
│ 文本类型: │
│ • str(字符串):"Hello", "Python" │
│ │
│ 布尔类型: │
│ • bool(布尔):True, False │
│ │
│ 空值类型: │
│ • NoneType(空值):None │
│ │
└─────────────────────────────────────────┘查看数据类型
# 使用 type() 函数查看类型
name = "Alice"
age = 25
height = 1.68
print(type(name)) # 输出:<class 'str'>
print(type(age)) # 输出:<class 'int'>
print(type(height)) # 输出:<class 'float'>检查变量类型
方法一:使用 type()
age = 25
# 检查类型
if type(age) == int:
print("age 是整数")问题: type() 无法正确判断继承关系
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(type(dog) == Animal) # False(错误!)方法二:使用 isinstance()(推荐)
isinstance: 检查对象是否属于指定类型,支持继承关系。
isinstance vs type:
┌─────────────────────────────────────────────────────────────┐
│ type() vs isinstance() │
│ │
│ type(obj) == SomeClass │
│ • 精确匹配类型 │
│ • 不考虑继承关系 │
│ │
│ isinstance(obj, SomeClass) │
│ • 检查类型及父类 │
│ • 支持继承关系 │
│ • 支持多类型判断 │
│ │
│ ⚠️ 推荐:使用 isinstance() │
└─────────────────────────────────────────────────────────────┘最简示例
age = 25
if isinstance(age, int):
print("age 是整数")关键代码解释
| 代码 | 含义 | 说明 |
|---|---|---|
isinstance(x, int) | 检查类型 | x 是否为 int 类型 |
isinstance(x, (int, str)) | 多类型检查 | x 是否为 int 或 str |
详细示例
# 支持多个类型判断
value = "hello"
if isinstance(value, (int, str)):
print("value 是整数或字符串")class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(isinstance(dog, Animal)) # True(正确!)type() vs isinstance()
┌─────────────────────────────────────────────────────┐
│ type() vs isinstance() 对比 │
├─────────────────────────────────────────────────────┤
│ │
│ type() isinstance() │
│ ─────── ─────────── │
│ • 检查确切类型 • 检查类型及父类 │
│ • 不考虑继承 • 考虑继承关系 │
│ • 返回类型对象 • 返回布尔值 │
│ • 适合精确匹配 • 更灵活、更安全 │
│ │
│ 使用场景: 使用场景: │
│ • 需要知道具体类型 • 类型检查(推荐) │
│ • 不涉及继承 • 判断对象类型 │
│ • 支持多类型判断 │
│ │
│ 示例: 示例: │
│ type(123) == int isinstance(123, int) │
│ type(123) isinstance(x, (A,B)) │
│ │
│ ❌ 不推荐用于类型检查 ✅ 推荐用于类型检查 │
│ │
└─────────────────────────────────────────────────────┘实际应用示例
def process_data(data):
"""处理不同类型的数据"""
if isinstance(data, str):
return data.upper()
elif isinstance(data, (int, float)):
return data * 2
elif isinstance(data, list):
return sum(data)
else:
return str(data)
# 测试
print(process_data("hello")) # HELLO
print(process_data(10)) # 20
print(process_data([1, 2, 3])) # 6
print(process_data({"a": 1})) # {'a': 1}字符串类型(str)
字符串(str): 用于表示文本数据,用引号括起来。
┌──────────────────────────────────────┐
│ 字符串创建方式 │
│ │
│ 'text' → 单引号字符串 │
│ "text" → 双引号字符串 │
│ """text""" → 多行字符串 │
│ r"text" → raw 字符串(不转义) │
│ f"Hi {x}" → 格式化字符串 │
└──────────────────────────────────────┘创建字符串
# 单引号或双引号
name1 = 'Alice'
name2 = "Alice"
# 包含引号的字符串
quote1 = 'He said, "Hello!"'
quote2 = "It's a beautiful day"
# 多行字符串
poem = """
静夜思
床前明月光,
疑是地上霜。
"""转义字符
常见转义字符:
\" → 双引号
\' → 单引号
\\ → 反斜杠
\n → 换行符
\t → 制表符(Tab)整数类型(int)
整数(int): 没有小数点的数字,Python 的整数没有大小限制。
┌──────────────────────────────────────┐
│ 整数字面量写法 │
│ │
│ 100 → 十进制(默认) │
│ 0b1010 → 二进制(0b 前缀) │
│ 0o12 → 八进制(0o 前缀) │
│ 0xA → 十六进制(0x 前缀) │
│ 1_000_000 → 下划线分隔(更易读) │
└──────────────────────────────────────┘# 正整数、负整数、零
positive = 100
negative = -50
zero = 0
# 不同进制
binary = 0b1010 # 二进制
octal = 0o12 # 八进制
hexadecimal = 0xA # 十六进制浮点数类型(float)
浮点数(float): 带小数点的数字,用于表示实数。
┌──────────────────────────────────────┐
│ 浮点数写法 │
│ │
│ 3.14 → 普通小数 │
│ -0.5 → 负数 │
│ 1.5e3 → 科学计数法(= 1500.0) │
│ 2.5e-2 → 科学计数法(= 0.025) │
│ │
│ ⚠️ 注意:0.1 + 0.2 ≠ 0.3(精度误差)│
└──────────────────────────────────────┘# 浮点数
pi = 3.14159
negative_float = -0.5
# 科学计数法
scientific = 1.5e3 # 1500.0
small = 2.5e-2 # 0.025
# 注意:浮点数计算可能有精度误差
result = 0.1 + 0.2
print(result) # 输出:0.30000000000000004布尔类型(bool)
布尔(bool): 只有两个值:True(真)和 False(假),是 int 的子类。
┌──────────────────────────────────────┐
│ 布尔值规则 │
│ │
│ True 假值(bool(x) == False): │
│ False → 0, 0.0, "", [], {}, None │
│ │
│ True 真值(bool(x) == True): │
│ True → 其他所有非零、非空值 │
│ │
│ bool 是 int 子类:True==1, False==0 │
└──────────────────────────────────────┘# 布尔类型只有两个值
is_true = True
is_false = False
# 比较运算的结果
print(5 > 3) # True
print(5 < 3) # False
# 真假值规则
print(bool(0)) # False
print(bool("")) # False
print(bool(1)) # True
print(bool("Hello")) # TrueNone 类型(空值)
什么是 None
None 是 Python 中表示"空"或"无"的特殊值,是 NoneType 类型的唯一值。
┌─────────────────────────────────────────┐
│ None 的含义 │
├─────────────────────────────────────────┤
│ │
│ None ≠ 0 (0 是数字) │
│ None ≠ "" ("" 是空字符串) │
│ None ≠ [] ([] 是空列表) │
│ None ≠ False (False 是布尔值) │
│ │
│ None 表示: │
│ • "没有值" │
│ • "未知" │
│ • "不存在" │
│ • "未设置" │
│ │
└─────────────────────────────────────────┘实际场景
场景1:函数没有返回值
def greet(name: str) -> None:
"""打招呼函数,没有返回值"""
print(f"你好,{name}!")
result = greet("张三") # 输出:你好,张三!
print(result) # 输出:None场景2:可选参数的默认值
def find_user(user_id: int) -> str | None:
"""查找用户,找不到返回 None"""
users = {1: "张三", 2: "李四"}
return users.get(user_id) # 找不到返回 None
name1 = find_user(1) # "张三"
name2 = find_user(99) # None(用户不存在)场景3:占位符
# 先定义变量,稍后赋值
config: dict[str, str] | None = None
# 后续加载配置
config = load_config()判断 None 的正确方式
x = None
# ✅ 推荐方式:使用 is
if x is None:
print("x 是 None")
# ❌ 不推荐:使用 ==
if x == None: # 能工作,但不符合规范
print("x 是 None")提示:为什么判断
None推荐用is?这涉及到 Python 的对象身份机制,我们将在下一节详细讲解。
None 与其他"空值"的区别
# 不同的"空"值
empty_str = "" # 空字符串
empty_list = [] # 空列表
empty_dict = {} # 空字典
zero = 0 # 数字零
false_val = False # 布尔假
none_val = None # 空值
# 布尔值转换
print(bool(empty_str)) # False
print(bool(empty_list)) # False
print(bool(empty_dict)) # False
print(bool(zero)) # False
print(bool(false_val)) # False
print(bool(none_val)) # False
# 但它们互不相等
print(None == "") # False
print(None == []) # False
print(None == 0) # False
print(None == False) # False类型提示中的 None
from typing import Optional
# 方式1:使用 Optional(Python 3.10 之前)
def find(id: int) -> Optional[str]:
...
# 方式2:使用 | None(Python 3.10+,推荐)
def find(id: int) -> str | None:
...变量比较:值与身份
在 Python 中,比较两个变量有两种含义:内容是否一样 vs 是不是同一个对象。
第一步:判断值是否相等 (==)
这是最直观的"等于"概念。
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True,内容一样第二步:查看内存身份 (id())
虽然内容一样,但它们是内存里的同一个列表吗? 用 id() 函数查看对象的"身份证号"(内存地址)。
a = [1, 2, 3]
b = [1, 2, 3]
print(id(a)) # 例如:140123...
print(id(b)) # 例如:140124... (地址不同!)结论:虽然 a == b,但它们是两个独立的对象。
第三步:判断身份是否相同 (is)
is 用于判断两个变量是否引用了同一个内存对象(等价于 id(a) == id(b))。
a = [1, 2, 3]
b = [1, 2, 3]
c = a # c 指向 a 的内存地址
print(a is b) # False(虽然内容一样,但是不同的对象)
print(a is c) # True(c 和 a 是同一个对象)深入理解:小整数池缓存
Python 为了效率,缓存了小整数(-5 到 256)。对于这些数字,is 可能会返回 True。
x = 100
y = 100
print(x is y) # True (在小整数池范围内,复用了同一个对象)
m = 300
n = 300
print(m is n) # False (超出范围,通常是不同对象)
print(m == n) # True (值依然相等)is 与 == 的核心区别总结
| 比较运算符 | 含义 | 比较依据 | 常用场景 |
|---|---|---|---|
== | 值相等 | 内容是否一样 | 比较数字、字符串、列表内容 |
is | 身份相同 | id() 是否一样 | 判断 None、判断单例对象 |
最佳实践:
- 比较数据用
== - 判断
None用is None
id() 函数与对象引用(进阶)
id() 不仅用于比较身份,还能帮我们理解 Python 的引用机制。
示例:修改引用对象的副作用
list1 = [1, 2, 3]
list2 = list1 # list2 指向了 list1 的内存地址
print(id(list1) == id(list2)) # True
# 修改 list2 会影响 list1 吗?
list2.append(4)
print(list1) # [1, 2, 3, 4] —— 是的!因为它们指向同一个对象。示例:创建副本
如果不想互相影响,需要创建新对象:
list1 = [1, 2, 3]
list2 = list(list1) # 创建新列表
print(list1 is list2) # False(不同对象)
list2.append(4)
print(list1) # [1, 2, 3] —— list1 不受影响类型转换
常见转换函数
┌─────────────────────────────────────────┐
│ 常见类型转换函数 │
├─────────────────────────────────────────┤
│ │
│ int(x) → 将 x 转换为整数 │
│ float(x) → 将 x 转换为浮点数 │
│ str(x) → 将 x 转换为字符串 │
│ bool(x) → 将 x 转换为布尔值 │
│ │
└─────────────────────────────────────────┘示例
# 字符串转整数
num_str = "123"
num_int = int(num_str)
print(num_int) # 输出:123
# 浮点数转整数(截断)
print(int(3.9)) # 输出:3
# 整数转字符串
num = 123
text = str(num)
print(text) # 输出:"123"从简单到复杂:变量的渐进应用
层级1:单个变量
# 最简单的变量
name: str = "Python"
print(name)层级2:多个变量
# 多个相关变量
name: str = "张三"
age: int = 18
score: float = 85.5
print(f"{name}, {age}岁, {score}分")层级3:变量交换
# Python 特有的变量交换
a: int = 10
b: int = 20
a, b = b, a # 一行完成交换
print(a, b) # 输出:20 10层级4:动态变量
# 变量可以改变类型(Python 动态类型特性)
x: int | str = 10
x = "现在是字符串"
print(x)层级5:变量与运算
# 变量参与计算
price: float = 99.9
quantity: int = 3
total: float = price * quantity
print(f"总价:{total:.2f}")综合应用:学生信息管理示例
这个示例综合运用变量和数据类型:
# 学生信息管理(Python 3.11+)
from typing import Any
def main() -> None:
# 存储多个学生信息
students: list[dict[str, Any]] = [
{"name": "张三", "age": 18, "scores": [85, 90, 78]},
{"name": "李四", "age": 19, "scores": [92, 88, 95]},
{"name": "王五", "age": 17, "scores": [78, 82, 88]}
]
# 处理每个学生数据
for student in students:
name: str = student["name"]
age: int = student["age"]
scores: list[int] = student["scores"]
# 计算平均分(变量运算)
average: float = sum(scores) / len(scores)
# 判断是否通过(布尔变量)
passed: bool = average >= 60
# 输出信息
print(f"{name}({age}岁): 平均分 {average:.1f} - {'通过' if passed else '未通过'}")
if __name__ == "__main__":
main()这个示例展示了:
- 不同类型变量的定义和使用
- 变量之间的运算
- 布尔变量的判断应用
- 变量在函数中的传递
- 现代类型提示语法
关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
list[dict[str, Any]] | 元素为字典的列表,字典值类型不限 | Any 表示值可以是 str、int、list 等任意类型 |
student["name"] | 通过键访问字典中的值 | 比索引更直观,一眼就知道取的是什么字段 |
sum(scores) / len(scores) | 计算列表元素的平均值 | sum 求总和,len 求元素数量,相除得平均 |
average >= 60 | 比较运算,结果为布尔值 | 将判断结果存入变量,代码更清晰可复用 |
{'通过' if passed else '未通过'} | f-string 内嵌三元表达式 | 根据布尔变量动态切换输出文字 |
类型提示入门(Python 3.6+)
为什么需要类型提示?
问题场景: 看这段代码,你能一眼看出 name 应该是什么类型吗?
name = get_user_name() # 是字符串?还是数字?使用类型提示后:
name: str = get_user_name() # 一眼就知道是字符串类型提示的好处:
- 代码更易读:一眼就知道变量应该是什么类型
- 编辑器更智能:VS Code 会提供自动补全
- 提前发现错误:某些工具可以检查类型错误
基本语法
变量类型注解:
# 语法:变量名: 类型 = 值
name: str = "张三"
age: int = 18
height: float = 1.68
is_student: bool = True函数类型注解:
# 参数和返回值都可以添加类型提示
def greet(name: str) -> str:
return f"你好,{name}"
def add(a: int, b: int) -> int:
return a + b常见类型
基本类型:
| 类型 | 说明 | 示例 |
|---|---|---|
str | 字符串 | name: str = "Python" |
int | 整数 | age: int = 18 |
float | 浮点数 | price: float = 99.9 |
bool | 布尔值 | is_valid: bool = True |
容器类型(简单了解):
# 列表
scores: list[int] = [85, 90, 78]
# 字典
student: dict[str, int] = {"张三": 85, "李四": 92}使用场景
什么时候使用类型提示?
✅ 推荐使用:
- 函数的参数和返回值
- 重要的变量定义
- 公共模块的代码
❌ 可以不用:
- 简单的临时变量
- 快速测试代码
- 学习练习时
示例:
# ✅ 好的做法:函数添加类型提示
def calculate_average(scores: list[int]) -> float:
return sum(scores) / len(scores)
# ❌ 可以不用:简单变量
x = 10 # 很明显是整数,不需要类型提示重要提醒
类型提示是可选的!
# 这两种写法都是正确的
name: str = "张三" # 有类型提示
name = "张三" # 没有类型提示Python 不会强制检查类型:
age: int = "十八" # 这行代码可以运行,但不符合类型提示类型提示的作用:
- 帮助人类理解代码
- 帮助编辑器提供提示
- 不是强制性的检查
小结
类型提示是"注释",不是"规则"
├── 让代码更易读
├── 帮助编辑器更智能
├── 不影响程序运行
└── 是可选的,不是必需的L2 实践层:用好
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
用 isinstance() 而非 type() 做类型检查 | 支持继承,更灵活 | isinstance(x, (int, str)) |
用 is None 而非 == None 检查空值 | None 是单例,is 检查身份,不会被子类重载 | if value is None: |
用 is 判断身份,用 == 判断值 | 语义清晰,各司其职 | a is b 检查同一对象 / a == b 检查值相等 |
| 类型提示用于函数签名 | 提高可读性和 IDE 支持 | def greet(name: str) -> str: |
| 多变量赋值使用元组解包 | 一行完成,Python 风格 | a, b = b, a |
变量命名使用 snake_case | Python 社区约定 | user_name 而非 userName |
变量值交换用 a, b = b, a | 无需临时变量,Python 特有 | a, b = b, a |
检查空容器用 if not items: | 惯用法,比 if len(items) == 0: 简洁 | if not items: |
反模式:不要这样做
# ❌ 错误:用 type() 做类型检查(不支持继承)
if type(obj) == Animal:
process(obj)
# ✅ 正确:用 isinstance()(支持继承)
if isinstance(obj, Animal):
process(obj)# ❌ 错误:用 == 判断 None
if value == None:
...
# ✅ 正确:用 is 判断 None
if value is None:
...# ❌ 错误:用 is 比较数值(小整数池导致不可靠行为)
if x is 100:
...
# ✅ 正确:用 == 比较数值
if x == 100:
...# ❌ 错误:变量名含连字符或数字开头
user-name = "Bob" # SyntaxError
2name = "Alice" # SyntaxError
# ✅ 正确:使用蛇形命名法
user_name = "Bob"
name2 = "Alice"# ❌ 错误:检查空列表用 len() 或 == []
if len(items) == 0:
...
if items == []:
...
# ✅ 正确:用真值判断惯用法
if not items:
...# ❌ 错误:用 not 判断 None(无法区分 None 和空值)
def process(value: str | None) -> str:
if not value: # None 和 '' 都会进入这里!
return "空值"
return value
# ✅ 正确:明确区分 None 和其他假值
def process(value: str | None) -> str:
if value is None:
return "无数据"
if not value:
return "空字符串"
return value常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 小整数池缓存 | -5~256 的小整数 is 可能返回 True,但不保证 | 数值比较用 ==,不用 is |
| 浮点精度误差 | 0.1 + 0.2 != 0.3 | 用 round() 或 math.isclose() |
| 可变默认参数 | 函数默认参数 [] 在所有调用间共享 | 用 None 替代默认值 |
| 浅拷贝引用 | list2 = list1 不创建新对象 | 用 list(list1) 或 copy.copy() |
bool 是 int 子类 | True == 1, False == 0,可能在类型检查中意外匹配 | 用 isinstance(x, bool) 先判断 bool |
| 字符串驻留 | 短字符串可能被缓存,is 可能意外返回 True | 字符串比较一律用 == |
| 变量类型动态变化 | x = 10 后 x = "str" 合法但易出错 | 保持变量类型一致性 |
适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 函数参数和返回值 | ✅ 推荐类型提示 | 接口清晰,IDE 友好 |
| 简单临时变量 | ❌ 可不加类型提示 | 类型一目了然 |
| 判断 None | ✅ 推荐 is None | 最安全、最 Pythonic |
| 判断数值相等 | ✅ 推荐 == | 比较值而非身份 |
| 需要多变量交换 | ✅ 推荐 a, b = b, a | Python 特有语法,一行完成 |
| 判断变量类型 | ✅ 推荐 isinstance() | 支持继承,更灵活 |
| 需要知道具体类型对象 | ✅ 用 type() | 返回类型对象,适合动态创建 |
| 检查空容器(列表/字典/字符串) | ✅ 推荐 if not x: | Python 惯用法 |
| 处理可选值(可能为 None) | ✅ 推荐 `str | None` 类型提示 |
| 金融/科学计算 | ❌ 不推荐直接 float 比较 | 用 decimal.Decimal 或 math.isclose() |
L3 专家层:深入
Python 如何实现变量
Python 变量不是"值容器",而是"名标签"——变量名通过引用指向内存中的对象。
Python 变量引用模型:
┌─────────────────────────────────────────────────────────────┐
│ 变量不是盒子,是标签 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ C/Java 模型(变量=盒子): │
│ ┌───┐ │
│ │ 10│ x = 10 → 变量 x 存储了值 10 │
│ └───┘ │
│ │
│ ✅ Python 模型(变量=标签): │
│ 堆内存对象 栈帧名称空间 │
│ ┌──────┐ ┌──────┐ │
│ │ 10 │ ←─────── │ x │ x = 10 → 标签 x 贴在对象上 │
│ │ref=1 │ └──────┘ │
│ └──────┘ │
│ │
│ x = 20 → 标签从旧对象撕下,贴到新对象上 │
│ 旧对象 refcount 减 1,若变为 0 则被回收 │
│ │
│ 引用计数(CPython):每个 PyObject 携带 ob_refcnt 字段 │
│ ob_refcnt = 0 → 对象被垃圾回收 │
│ │
└─────────────────────────────────────────────────────────────┘验证代码:
import sys
# 引用计数
a = "hello"
print(sys.getrefcount(a)) # 引用计数(含 getrefcount 自身临时引用)
# 小整数缓存:-5 到 256
x = 256
y = 256
print(x is y) # True(命中小整数池)
x = 257
y = 257
print(x is y) # 在 CPython 中通常 True(脚本级编译优化),但不应依赖
# 字符串驻留(intern)
a = "hello"
b = "hello"
print(a is b) # True(短字符串常量自动驻留)
a = "hello world!"
b = "hello world!"
print(a is b) # True(标识符格式字符串自动驻留)
# 查看变量绑定的字节码
from dis import dis
dis("x = 10")
# LOAD_CONST 0 (10)
# STORE_NAME 0 (x)
# Float 的 IEEE 754 精度问题
f = 0.1 + 0.2
print(repr(f)) # 0.30000000000000004
print(f == 0.3) # False
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.3类型系统内部实现
类型检查的内部机制:
┌─────────────────────────────────────────────────────────────┐
│ type() vs isinstance() 实现差异 │
├─────────────────────────────────────────────────────────────┤
│ │
│ type(obj) 直接返回 obj.__class__ │
│ → type(obj) == SomeClass → 只比较类对象本身 │
│ │
│ isinstance(obj, SomeClass) 遍历 MRO: │
│ → SomeClass in type(obj).__mro__ ? │
│ → 支持鸭子类型(通过 __instancecheck__ 协议) │
│ │
│ None 是单例: │
│ → NoneType 只有一个实例,全局共享 │
│ → None.__class__ 是 NoneType │
│ → 因此用 is 而非 ==(后者可被 __eq__ 重载) │
│ │
│ bool 是 int 子类: │
│ → issubclass(bool, int) # True │
│ → True == 1, False == 0 │
│ → 但 type(True) is bool,type(1) is int │
│ → isinstance(True, int) 也是 True — 这是陷阱! │
│ → 如需区分 bool 和 int,先用 type(x) is bool │
│ │
│ 可变对象 vs 不可变对象: │
│ → 不可变:int, float, str, tuple, frozenset, bool, None │
│ → 可变:list, dict, set, bytearray │
│ → 不可变对象修改 = 创建新对象;可变对象修改 = 原地修改 │
│ │
└─────────────────────────────────────────────────────────────┘验证代码:
# 查看 MRO(方法解析顺序)
print(int.__mro__)
# (<class 'int'>, <class 'object'>)
print(bool.__mro__)
# (<class 'bool'>, <class 'int'>, <class 'object'>)
# bool 是 int 子类的验证
print(issubclass(bool, int)) # True
print(isinstance(True, int)) # True ← 注意!
print(isinstance(True, bool)) # True
# 可变 vs 不可变
x = 10
print(id(x)) # 例如 4304901232
x = x + 1 # 创建新对象
print(id(x)) # 不同的 id!
lst = [1, 2]
print(id(lst)) # 例如 4372290112
lst.append(3) # 原地修改
print(id(lst)) # 相同 id!性能考量
| 操作 | 复杂度 | 说明 |
|---|---|---|
变量赋值 x = value | O(1) | 引用绑定,无拷贝 |
isinstance(x, T) | O(n) | n = MRO 长度,通常 ≤ 3 |
type(x) == T | O(1) | 直接属性访问 |
小整数 x is y (≤256) | O(1) | 命中缓存,同一对象 |
大整数 x is y (>256) | 不保证 | 取决于编译优化 |
| 浮点运算 | 取决于精度 | 受 IEEE 754 影响 |
| 字符串拼接(少量) | O(n) | CPython 有优化 |
| 字符串拼接(循环) | O(n²) | 用 "".join() 优化到 O(n) |
import timeit
# isinstance vs type 性能
print(timeit.timeit("isinstance(42, int)", number=10_000_000))
# → ~0.5 秒
print(timeit.timeit("type(42) is int", number=10_000_000))
# → ~0.4 秒(略快,但不考虑继承)
# 变量赋值的代价(极小)
print(timeit.timeit("x = 42", number=10_000_000))
# → ~0.2 秒知识关联
变量与数据类型知识关联:
┌─────────────┐
│ Python对象 │
│ 模型 │
│ PyObject │
└─────────────┘
│
┌───────────────┼───────────────┐
↓ ↓ ↓
┌───────────┐ ┌─────────────┐ ┌───────────┐
│ 引用计数 │ │ 垃圾回收 │ │ 小整数池 │
│ refcount │ │ GC │ │ -5 ~ 256 │
└───────────┘ └─────────────┘ └───────────┘
│
↓
┌───────────┐ ┌─────────────┐ ┌───────────┐
│ id() │────→│ is / is not │←───│ NoneType │
│ 内存地址 │ │ 身份判断 │ │ 单例模式 │
└───────────┘ └─────────────┘ └───────────┘
│
↓
┌───────────┐ ┌─────────────┐
│ == / != │ │ isinstance │
│ 值比较 │ │ MRO 遍历 │
└───────────┘ └─────────────┘
│
↓
┌───────────────┐
│ 不可变类型 │ int, float, str, tuple, frozenset, bool, None
│ vs 可变类型 │ list, dict, set, bytearray
└───────────────┘本章小结
┌─────────────────────────────────────────────────────────────┐
│ 变量与数据类型 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 变量命名: │
│ ✓ 字母/数字/下划线,不能以数字开头 │
│ ✓ 使用 snake_case 命名风格 │
│ │
│ 数据类型: │
│ ✓ int(整数)、float(浮点数) │
│ ✓ str(字符串)、bool(布尔值) │
│ ✓ NoneType(空值):None │
│ │
│ 类型转换: │
│ ✓ int()、float()、str()、bool() │
│ │
│ 类型提示: │
│ ✓ 基本语法:变量名: 类型 = 值 │
│ ✓ 函数注解:def func(x: int) -> str: │
│ ✓ 是可选的,让代码更易读 │
│ │
│ 类型检查: │
│ ✓ isinstance() 检查类型 │
│ ✓ type() 查看具体类型 │
│ │
│ None 类型: │
│ ✓ 表示"空"或"无" │
│ ✓ 判断用 is None,不用 == None │
│ ✓ Optional[str] 等价于 str | None │
│ │
│ 对象标识: │
│ ✓ id() 获取对象唯一标识 │
│ ✓ is 判断是否同一对象 │
│ ✓ == 判断值是否相等 │
│ │
└─────────────────────────────────────────────────────────────┘