- Published on
2.8.变量与内存
- Authors

- Name
- xiaobai
1.变量是什么?
1.1.变量的本质
在 Python 中,变量不是存储数据的容器,而是贴在对象上的标签。这是理解 Python 变量最重要的概念。
a = 10
这行代码的真实含义:
- 内存中创建一个整数对象
10创建一个名为a的变量(标签) - 将标签
a贴在对象10上(建立引用关系)
Python 的变量(标签模型):
变量 a ────→ [整数对象 10]
(内存地址: 0x1000)
- Python 中,变量是对象的引用,指向内存中的对象

2.变量的命名规则
2.1.硬性规则(必须遵守)
| 规则 | 说明 | 示例 |
|---|---|---|
| 字符限制 | 只能包含字母、数字、下划线 | name_1, _value ✅ |
| 开头字符 | 不能以数字开头 | 2name ❌ |
| 大小写敏感 | 大小写是不同的变量 | age ≠ Age ≠ AGE |
| 不能是关键字 | 不能使用 Python 保留字 | if, for, class ❌ |
# 合法的变量名
name = "Alice"
user_age = 25
_country = "USA"
value2 = 100
# 非法的变量名
2name = "Bob" # 错误!以数字开头
user-age = 30 # 错误!包含连字符
class = "Math" # 错误!class 是关键字
2.2.风格约定(强烈推荐)
遵循 PEP 8 编码规范:
# 推荐:蛇形命名法(snake_case)
user_name = "Charlie"
total_count = 42
file_path = "/docs/report.pdf"
# 不推荐:驼峰命名法(用于类名)
userName = "Charlie"
TotalCount = 42
# 见名知意
email_address = "user@example.com"
max_retry_count = 3
# 不清晰
e = "user@example.com"
mrc = 3
2.3.特殊命名约定
# 单下划线开头:内部使用,不建议外部访问
_internal_var = "private"
# 双下划线开头:类的私有成员
__private_var = "really private"
# 双下划线前后:魔术方法/特殊方法
__init__, __str__, __len__
# 单下划线:临时变量或不重要的变量
for _ in range(5):
print("Hello")
__init__:构造方法,用于初始化对象。__str__:定义对象的字符串表示,通常用于print()输出。__len__:定义对象的长度,支持len()函数。
这些被称为“魔术方法”或“特殊方法”,在类中有特殊用途。
3.变量的赋值
3.1.基本赋值
# 基本类型赋值
message = "Hello, World!"
pi = 3.14159
is_active = True
count = 0
3.2.多重赋值
# 同时为多个变量赋同一个值
a = b = c = 100
print(a, b, c) # 100 100 100
# 同时为多个变量赋不同的值(元组解包)
name, age, city = "Alice", 30, "Beijing"
print(name) # Alice
print(age) # 30
print(city) # Beijing
3.3.变量交换
Python 中交换变量非常优雅:
# Python 方式(推荐)
x, y = 10, 20
x, y = y, x
print(x, y) # 20 10
# 传统方式(不推荐)
x, y = 10, 20
temp = x
x = y
y = temp
print(x, y) # 20 10
3.4.链式赋值的陷阱
# 注意:共享同一个对象
a = b = [1, 2, 3]
a.append(4)
print(b) # [1, 2, 3, 4] - b 也被修改了!
# 正确方式:分别赋值
a = [1, 2, 3]
b = [1, 2, 3] # 创建新对象
a.append(4)
print(b) # [1, 2, 3] - b 没有被修改
4.深入理解:变量与对象
4.1.一切皆对象
在 Python 中,一切都是对象,包括数字、字符串、函数、类等。
# 数字是对象
x = 42
print(type(x)) # <class 'int'>
print(isinstance(x, object)) # True
# 函数也是对象
def greet():
return "Hello"
print(type(greet)) # <class 'function'>
print(isinstance(greet, object)) # True
type(obj):用于查看对象的类型。例如,type(123)返回<class 'int'>,type("abc")返回<class 'str'>。isinstance(obj, class_or_tuple):判断一个对象是否是某个类型(或类型元组)的实例。例如,isinstance(123, int)返回True,isinstance("abc", (int, str))返回True。
4.2.动态类型
变量本身没有类型,类型属于对象。同一个变量可以指向不同类型的对象。
var = 100
print(var, type(var)) # 100 <class 'int'>
var = "Now I'm a string"
print(var, type(var)) # Now I'm a string <class 'str'>
var = [1, 2, 3]
print(var, type(var)) # [1, 2, 3] <class 'list'>
var = {"key": "value"}
print(var, type(var)) # {'key': 'value'} <class 'dict'>
4.3.对象的身份:id() 和 is
# id() 返回对象的唯一标识(内存地址)
a = [1, 2, 3]
print(id(a)) # 例如:140245678901234
# is 检查是否是同一个对象
b = a
c = [1, 2, 3]
print(a is b) # True - 指向同一个对象
print(a is c) # False - 指向不同的对象
print(a == c) # True - 内容相同
# 查看 id
print(id(a)) # 140245678901234
print(id(b)) # 140245678901234 (相同)
print(id(c)) # 140245678905678 (不同)
重要区别:
is:比较对象的身份(内存地址是否相同)==:比较对象的值(内容是否相同)
5.内存指向关系
5.1.多个变量指向同一个对象
a = 100
b = a
内存图示:
变量 a ────┐
↓
[整数对象 100]
↑
变量 b ────┘
(地址: 0x2000)
验证代码:
a = 100
b = a
print(a is b) # True - 指向同一个对象
print(id(a) == id(b)) # True - 内存地址相同
# 修改 a 会怎样?
a = 200
print(a) # 200
print(b) # 100 - b 仍然指向原来的对象
# 内存变化
print(a is b) # False - 现在指向不同对象
关键理解:a = 200 不是修改了对象 100 的值,而是创建了新对象 200,然后让 a 指向这个新对象。

5.2.相同值的不同对象
a = [1, 2, 3]
b = [1, 2, 3]
内存图示:
变量 a ───→ [列表对象 [1,2,3]]
(地址: 0x3000)
变量 b ───→ [列表对象 [1,2,3]]
(地址: 0x4000)
验证代码:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - 值相等
print(a is b) # False - 不是同一个对象
print(id(a), id(b)) # 两个不同的地址
# 修改 a 不会影响 b
a.append(4)
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3]

5.3.小整数缓存机制
Python 对整数和短字符串进行了缓存优化(驻留机制)。
# 整数缓存
a = 100
b = 100
print(a is b) # True - 指向同一个缓存对象
# 字符串驻留
s1 = "hello"
s2 = "hello"
print(s1 is s2) # True - 指向同一个对象
# 包含特殊字符的字符串可能不驻留
s3 = "hello"*10000
s4 = "hello"*10000
print(s3 is s4) # 可能是 False
6.可变对象 vs 不可变对象
这是理解 Python 内存管理的最关键部分!
6.1.不可变对象(Immutable)
不可变对象一旦创建,其值就不能被修改。
常见的不可变类型包括:
int(整数):一旦赋值,数值不可更改。float(浮点数):数值不可更改。str(字符串):内容不可更改,任何修改都会生成新字符串对象。tuple(元组):元素不可更改。frozenset(冻结集合):集合内容不可更改。bytes(字节串):内容不可更改。
这些类型的对象在创建后,其内部数据不能被修改。如果对它们进行“修改”操作,实际上是创建了一个新的对象,原对象保持不变。
# 数字示例
x = 5
print(f"x 的地址: {id(x)}")
x = x + 1 # 不是修改 5,而是创建新对象 6
print(f"x 的新地址: {id(x)}") # 地址改变了
# 字符串示例
s = "hello"
print(f"s 的地址: {id(s)}")
s = s + " world" # 创建新字符串
print(f"s 的新地址: {id(s)}") # 地址改变了
# 元组示例
t = (1, 2, 3)
print(f"t 的地址: {id(t)}")
# t[0] = 99 # 错误!元组不可修改
t = (1, 2, 3, 4) # 创建新元组
print(f"t 的新地址: {id(t)}") # 地址改变了
不可变对象的特点:
- 修改操作会创建新对象
- 多个变量可以安全地共享同一个不可变对象
- 可以作为字典的键
6.2.可变对象(Mutable)
可变对象的值可以在原地修改,不创建新对象。
list(列表):可以在原地添加、删除或修改元素。dict(字典):可以动态添加、删除或修改键值对。set(集合):可以添加或移除元素,集合内容可变。bytearray(可变字节序列):可以修改其中的字节内容。
# 列表示例
lst = [1, 2, 3]
print(f"列表地址: {id(lst)}")
lst.append(4) # 原地修改
print(f"append 后: {lst}")
print(f"列表地址: {id(lst)}") # 地址不变
lst[0] = 99 # 原地修改元素
print(f"修改后: {lst}")
print(f"列表地址: {id(lst)}") # 地址仍不变
# 字典示例
d = {"name": "Alice", "age": 25}
print(f"字典地址: {id(d)}")
d["city"] = "Beijing" # 原地添加键值对
print(f"添加后: {d}")
print(f"字典地址: {id(d)}") # 地址不变
可变对象的特点:
- 可以在原地修改
- 多个变量共享可变对象时需要小心
- 不能作为字典的键
6.3.可变对象的共享风险
当多个变量引用同一个可变对象时,对其中一个变量的修改会影响到所有引用该对象的变量。这种“共享”带来的副作用,容易导致程序出现难以察觉的 bug。
例如,列表、字典等可变对象在赋值时只是复制了引用(地址),并没有创建新对象。如果需要避免这种风险,应当使用 .copy() 方法或切片 [:] 来创建副本。
常见风险场景包括:
- 作为函数参数传递可变对象,函数内部的修改会影响外部变量。
- 多个变量指向同一个可变对象,任意一个变量的修改都会影响其他变量。
# 风险示例
list1 = [1, 2, 3]
list2 = list1 # list2 和 list1 指向同一个列表
list1.append(4)
print(list2) # [1, 2, 3, 4] - list2 也被修改了!
# 安全方式:创建副本
list1 = [1, 2, 3]
list2 = list1.copy() # 或 list2 = list1[:]
list1.append(4)
print(list1) # [1, 2, 3, 4]
print(list2) # [1, 2, 3] - list2 不受影响
6.4.不可变对象包含可变对象
有些不可变对象(如元组)可以包含可变对象(如列表、字典)。虽然元组本身是不可变的,但其内部的可变对象内容是可以被修改的。这种情况下,元组的“不可变性”只体现在其结构(元素的引用)不能更改,但元素所指向的可变对象内容可以发生变化。
示例:
# 元组是不可变的,但可以包含可变对象
t = (1, 2, [3, 4])
# 不能修改元组本身
# t[0] = 99 # 错误!
# 但可以修改元组中的可变对象
t[2].append(5)
print(t) # (1, 2, [3, 4, 5])
# 元组的 id 没变,但内容变了
7.以下内容不用看,Python全部学完后再看
8.实际应用场景
8.1.函数参数传递
Python 的参数传递是传递对象引用(有时称为"传对象引用")。
- 传递可变对象:函数内的修改会影响外部
- 传递不可变对象:函数内的修改不会影响外部
- 函数内重新赋值:不会影响外部变量
def modify_list(lst):
"""修改列表(可变对象)"""
lst.append(4)
print(f"函数内 lst: {lst}, id: {id(lst)}")
def modify_number(num):
"""修改数字(不可变对象)"""
num = num + 1
print(f"函数内 num: {num}, id: {id(num)}")
def reassign_list(lst):
"""重新赋值列表"""
lst = [7, 8, 9] # 创建新对象
print(f"函数内 lst: {lst}, id: {id(lst)}")
# 测试可变对象
my_list = [1, 2, 3]
print(f"调用前: {my_list}, id: {id(my_list)}")
modify_list(my_list)
print(f"调用后: {my_list}") # [1, 2, 3, 4] - 被修改了!
# 测试不可变对象
my_num = 10
print(f"\n调用前: {my_num}, id: {id(my_num)}")
modify_number(my_num)
print(f"调用后: {my_num}") # 10 - 没有被修改
# 测试重新赋值
print(f"\n调用前: {my_list}, id: {id(my_list)}")
reassign_list(my_list)
print(f"调用后: {my_list}") # [1, 2, 3, 4] - 没有被修改
8.2.默认参数的陷阱
默认参数的陷阱主要出现在可变对象(如列表、字典等)作为函数默认参数时。 Python 只在函数定义时计算一次默认参数,这意味着如果默认参数是可变对象,后续对它的修改会影响到后续的函数调用。
# 危险:可变对象作为默认参数
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] - 意外!
print(add_item(3)) # [1, 2, 3] - 意外!
# 正确方式
def add_item_safe(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(add_item_safe(1)) # [1]
print(add_item_safe(2)) # [2]
print(add_item_safe(3)) # [3]
上面例子中,add_item 的 lst=[] 只在函数定义时创建了一次。每次调用 add_item 时,如果没有传入 lst,都会用同一个列表对象。这会导致意外的累积效果。
正确做法:将默认参数设为 None,在函数内部判断并创建新对象,避免共享同一个可变对象。
这样可以保证每次调用函数时,默认参数都是一个全新的列表,不会互相影响。
8.3.浅拷贝 vs 深拷贝
浅拷贝(shallow copy)和深拷贝(deep copy)是 Python 中用于复制对象的两种方式,尤其在处理包含嵌套可变对象(如列表、字典等)的复合对象时非常重要。
- 浅拷贝:只复制最外层对象本身,内部嵌套的对象依然是原对象的引用。也就是说,外层是新对象,但内层还是指向同一个内存地址。如果修改内层可变对象,原对象和拷贝对象都会受到影响。
- 深拷贝:不仅复制最外层对象,还递归地复制所有嵌套的对象。这样,原对象和拷贝对象完全独立,互不影响。
常见的拷贝方式有:
- 直接赋值(
b = a):不会创建新对象,a和b指向同一个对象。 - 浅拷贝(如
b = a.copy()、b = copy.copy(a)、b = a[:]):只复制外层。 - 深拷贝(
b = copy.deepcopy(a)):完全递归复制所有层级。
import copy
# 原始列表(包含嵌套列表)
original = [1, 2, [3, 4]]
# 1. 直接赋值(共享引用)
assigned = original
assigned[0] = 99
print(original) # [99, 2, [3, 4]] - 被修改了
# 2. 浅拷贝(只拷贝第一层)
original = [1, 2, [3, 4]]
shallow = copy.copy(original)
# 或 shallow = original.copy()
# 或 shallow = original[:]
print(original is shallow) # False - 外层是新对象
print(original[2] is shallow[2]) # True - 内层是同一个对象
shallow[0] = 100
print(original) # [1, 2, [3, 4]] - 没有被影响
shallow[2].append(5)
print(original) # [1, 2, [3, 4, 5]] - 被影响了!
# 3. 深拷贝(完全独立的副本)
original = [1, 2, [3, 4]]
deep = copy.deepcopy(original)
print(original[2] is deep[2]) # False - 完全独立
deep[2].append(5)
print(original) # [1, 2, [3, 4]] - 没有被影响
print(deep) # [1, 2, [3, 4, 5]]
拷贝方法对比:
| 方法 | 语法 | 外层对象 | 内层对象 | 使用场景 |
|---|---|---|---|---|
| 赋值 | b = a | 共享 | 共享 | 需要多个引用同一对象 |
| 浅拷贝 | b = a.copy() | 独立 | 共享 | 只需要独立的第一层 |
| 深拷贝 | b = copy.deepcopy(a) | 独立 | 独立 | 需要完全独立的副本 |
8.4.循环引用和垃圾回收
循环引用是指对象之间互相引用,形成一个“环”,导致它们的引用计数都不为零,从而无法被简单的引用计数机制回收。例如,A 引用 B,B 又引用 A,即使外部变量都被删除,A 和 B 依然互相持有对方的引用。
Python 的垃圾回收机制(GC)除了引用计数外,还采用了“分代垃圾回收”算法,能够检测并回收这种循环引用的对象。常用的 gc 模块可以手动触发垃圾回收,也可以查看当前不可达但未被回收的对象。
注意事项:
- 带有
__del__方法的对象如果参与循环引用,可能不会被及时回收,因为解释器无法确定回收顺序,可能导致资源泄漏。 - 一般情况下无需手动干预,除非处理大量对象或特殊资源管理需求。
import gc
class Node:
def __init__(self, value):
self.value = value
self.next = None
def __del__(self):
print(f"Node {self.value} 被回收")
# 创建循环引用
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # 形成环
# 删除引用
del node1
del node2
# 手动触发垃圾回收
print("触发垃圾回收...")
gc.collect() # Python 的垃圾回收器会处理循环引用
9.最佳实践
9.1.变量命名
良好的变量命名有助于代码的可读性和可维护性。建议遵循以下原则:
- 见名知意:变量名应能准确反映其用途或含义,避免使用无意义的缩写。
- 遵循命名规范:Python 推荐使用小写字母和下划线(snake_case)风格命名变量。
- 避免与关键字或内置函数重名:如
list,str,id等。 - 适当使用前缀/后缀:如
is_、has_、num_、_list等,提升语义清晰度。 - 长度适中:变量名不宜过短或过长,能表达清楚含义即可。
# 好的命名
user_email = "user@example.com"
max_retry_count = 3
is_authenticated = True
total_price = 99.99
# 不好的命名
e = "user@example.com"
mrc = 3
flag = True
tp = 99.99
9.2.使用 is 和 == 的时机
在 Python 中,is 和 == 虽然都可以用来进行比较,但它们的含义和适用场景不同:
is用于判断两个变量是否引用自同一个对象(即对象的身份是否相同)。常用于和None、True、False等单例对象的比较,或需要判断两个变量是否为同一对象时。==用于判断两个对象的值是否相等(即内容是否相同)。如果对象实现了__eq__方法,则会调用该方法进行值的比较。
最佳实践:
- 判断是否为
None,应使用is或is not,而不是==。 - 判断两个变量是否为同一对象,使用
is。 - 判断值是否相等,使用
==。 - 对于布尔值判断,直接用变量本身即可,无需
== True或== False。
# 检查是否为 None(使用 is)
value = None
if value is None: # 正确
print("value 是 None")
if value == None: # 不推荐
print("value 是 None")
# 检查布尔值
is_active = True
if is_active: # 简洁
print("活跃")
if is_active == True: # 冗余
print("活跃")
# 比较对象身份(使用 is)
a = [1, 2, 3]
b = a
if a is b: # 检查是否是同一个对象
print("同一个对象")
# 比较对象值(使用 ==)
c = [1, 2, 3]
if a == c: # 检查值是否相等
print("值相等")
9.3.避免可变对象的陷阱
在 Python 中,变量可以引用可变对象(如列表、字典、集合等)或不可变对象(如整数、字符串、元组等)。可变对象的一个常见陷阱是:多个变量可能引用同一个可变对象,导致对其中一个变量的修改会影响到其他变量。
9.3.1.可变默认参数的陷阱
函数定义时如果使用可变对象作为默认参数,这个对象会在函数定义时被创建,并在后续所有调用中被共享。例如:
# 危险:可变默认参数
def append_to(element, target=[]):
target.append(element)
return target
# 安全:使用 None
def append_to_safe(element, target=None):
if target is None:
target = []
target.append(element)
return target
print(append_to(1))
print(append_to(2))
print(append_to_safe(1))
print(append_to_safe(2))
9.3.2.共享可变对象
在 Python 中,使用 [[0] * cols] * rows 创建二维列表时,实际上是将同一个子列表的引用复制多次。这样导致所有的行实际上指向同一个列表对象,对其中一行的修改会影响到所有行。这是因为可变对象(如列表)在复制引用时不会创建新的对象。
# 危险:共享可变对象
def create_matrix(rows, cols):
return [[0] * cols] * rows # 所有行共享同一个列表
# 安全:创建独立对象
def create_matrix_safe(rows, cols):
return [[0] * cols for _ in range(rows)]
matrix = create_matrix(3, 3)
matrix[0][0] = 1
print(matrix)
matrix_safe = create_matrix_safe(3, 3)
matrix_safe[0][0] = 1
print(matrix_safe)
9.4.合理使用拷贝
在 Python 中,拷贝对象时需要根据实际需求选择浅拷贝(shallow copy)还是深拷贝(deep copy)。
- 浅拷贝:只复制最外层对象,内部嵌套的可变对象依然是原来的引用。常用方法有
list.copy()、切片[:]、copy.copy()等。 - 深拷贝:递归复制所有嵌套对象,生成完全独立的新对象。使用
copy.deepcopy()实现。
# 简单列表:使用浅拷贝
simple_list = [1, 2, 3, 4]
copy1 = simple_list.copy()
copy2 = simple_list[:]
copy3 = list(simple_list)
# 嵌套结构:使用深拷贝
import copy
nested_list = [1, [2, 3], [4, [5, 6]]]
deep_copy = copy.deepcopy(nested_list)
9.5.调试内存问题
import sys
def analyze_object(obj, name):
"""分析对象的内存信息"""
print(f"\n{name} 的分析:")
print(f" 类型: {type(obj)}")
print(f" 值: {obj}")
print(f" ID: {id(obj)}")
print(f" 大小: {sys.getsizeof(obj)} bytes")
print(f" 引用计数: {sys.getrefcount(obj)}")
# 使用示例
my_list = [1, 2, 3]
analyze_object(my_list, "my_list")
another_ref = my_list
analyze_object(my_list, "my_list (多了一个引用)")
getsizeof:用于获取对象占用的内存字节数(不包括其引用的子对象)。getrefcount:用于获取对象当前的引用计数(即有多少个引用指向该对象)。
10.总结
10.1.核心要点
- 变量是标签,不是容器
- 变量存储的是对象的引用,不是对象本身
- 理解这点是掌握 Python 的关键
- 对象的身份和值
- 使用
id()查看对象的身份(内存地址) - 使用
is比较身份,使用==比较值
- 使用
- 可变 vs 不可变
- 不可变对象:修改时创建新对象(int, str, tuple)
- 可变对象:可以原地修改(list, dict, set)
- 参数传递机制
- Python 传递对象引用
- 修改可变对象会影响外部
- 重新赋值不会影响外部
- 拷贝的选择
- 赋值:共享引用
- 浅拷贝:独立外层,共享内层
- 深拷贝:完全独立
10.2.避免常见错误
- 可变对象作为默认参数
- 忽略浅拷贝和深拷贝的区别
- 使用
==比较 None - 不理解函数参数的引用传递
- 在循环中创建共享的可变对象

