万物皆对象,名存地址
在 Python 中,所有的数据,无论是整数、字符串、列表、函数,甚至是一个模块,都是以对象 的形式存在的,当你创建一个变量时,a = 10,你并没有在变量 a 中“存放”数字 10 本身,相反,你做的事情是:

- 在内存中创建一个代表数字
10的对象。 - 创建一个名为
a的名字(变量名)。 - 将名字
a和这个内存对象的地址 关联起来。
这个关联关系,就是引用。a 是一个引用,它指向了数字 10 这个对象。
我们可以用一个简单的比喻来理解:
- 对象:是你家的一栋房子(实体)。
- 变量名:是写在这栋房子上的门牌号(标签)。
- 引用:是门牌号和房子之间的对应关系。
多个门牌号可以指向同一栋房子,同样,多个变量名也可以引用同一个对象。
引用的可视化与 id() 函数
Python 提供了 id() 函数,可以让我们查看一个对象在内存中的唯一标识符(可以理解为它的地址),通过观察 id() 的变化,我们可以直观地理解引用的工作方式。

# 示例 1:基本赋值
a = 10
print(f"a = {a}, id(a) = {id(a)}")
# 将 a 的引用赋给 b
b = a
print(f"b = {b}, id(b) = {id(b)}")
# a 和 b 指向同一个对象
print(f"a is b: {a is b}") # is 关键字检查两个引用是否指向同一个对象
# 输出:
# a = 10, id(a) = 140736688229600 (你的地址会不同)
# b = 10, id(b) = 140736688229600
# a is b: True
从上面的例子可以看出,b = a 并没有复制数字 10,而是让变量 b 也指向了 a 所指向的那个对象。
可变对象与不可变对象
这是理解引用行为时最重要的一点,也是 Python 中许多常见 Bug 的根源。
1 不可变对象
不可变对象 指的是对象一旦被创建,其内部的值就不能被修改,如果尝试修改,Python 会创建一个全新的对象。
常见的不可变对象包括:
- 数字 (
int,float,complex) - 字符串 (
str) - 元组 (
tuple) - 布尔值 (
bool) frozenset(冻结集合)
# 示例 2:不可变对象 (整数)
a = 10
print(f"初始: a = {a}, id(a) = {id(a)}")
# 尝试修改 a
a = a + 1 # 这行代码的实质是:1. 创建一个新对象 11; 2. 让 a 引用这个新对象
print(f"修改后: a = {a}, id(a) = {id(a)}")
# 检查 b 是否还指向原来的对象
b = 10
print(f"b = {b}, id(b) = {id(b)}") # b 仍然指向原来的对象 10
print(f"a is b: {a is b}") # False, 因为 a 现在指向 11
# 输出:
# 初始: a = 10, id(a) = 140736688229600
# 修改后: a = 11, id(a) = 140736688229632 (地址变了!)
# b = 10, id(b) = 140736688229600
# a is b: False
2 可变对象
可变对象 指的是对象在被创建后,其内部的值可以被修改,修改操作不会创建新对象,而是在原对象上进行。
常见的可变对象包括:
- 列表 (
list) - 字典 (
dict) - 集合 (
set) - 自定义类的实例
# 示例 3:可变对象 (列表)
my_list = [1, 2, 3]
print(f"初始: my_list = {my_list}, id(my_list) = {id(my_list)}")
# another_list 引用 my_list 指向的同一个列表
another_list = my_list
print(f"赋值后: another_list = {another_list}, id(another_list) = {id(another_list)}")
# 修改 my_list (注意是修改其内容,而不是重新赋值)
my_list.append(4)
print(f"my_list.append(4) 后:")
print(f"my_list = {my_list}, id(my_list) = {id(my_list)}")
print(f"another_list = {another_list}, id(another_list) = {id(another_list)}")
# another_list 也被改变了!因为它们引用的是同一个对象。
# 输出:
# 初始: my_list = [1, 2, 3], id(my_list) = 2323455663976
# 赋值后: another_list = [1, 2, 3], id(another_list) = 2323455663976
# my_list.append(4) 后:
# my_list = [1, 2, 3, 4], id(my_list) = 2323455663976 (地址没变)
# another_list = [1, 2, 3, 4], id(another_list) = 2323455663976
函数参数传递:传引用还是传值?
这是一个经典的面试题,Python 的函数参数传递机制是“传对象引用”(Pass-by-object-reference)。
这意味着,当你把一个变量作为参数传递给函数时,传递的是该变量所指向的对象的引用(地址),而不是变量本身或对象的副本。
这会产生两种情况,取决于对象是可变的还是不可变的。
1 传递不可变对象
函数内部无法修改原始对象,因为它不能被修改,任何“修改”操作都会创建一个新对象,并且这个新对象只在函数的局部作用域内有效。
def modify_immutable(x):
print(f"函数内 - 修改前: x = {x}, id(x) = {id(x)}")
x = x + 10 # 创建了一个新对象 20
print(f"函数内 - 修改后: x = {x}, id(x) = {id(x)}")
return x
my_var = 10
print(f"函数外 - 调用前: my_var = {my_var}, id(my_var) = {id(my_var)}")
# my_var 的引用被传递给 x
result = modify_immutable(my_var)
print(f"函数外 - 调用后: my_var = {my_var}, id(my_var) = {id(my_var)}")
print(f"函数返回值: result = {result}")
# 输出:
# 函数外 - 调用前: my_var = 10, id(my_var) = 140736688229600
# 函数内 - 修改前: x = 10, id(x) = 140736688229600
# 函数内 - 修改后: x = 20, id(x) = 140736688229936
# 函数外 - 调用后: my_var = 10, id(my_var) = 140736688229600 # my_var 未被改变
# 函数返回值: result = 20
2 传递可变对象
函数内部可以通过引用来修改原始对象的内容。
def modify_mutable(lst):
print(f"函数内 - 修改前: lst = {lst}, id(lst) = {id(lst)}")
lst.append(99) # 修改了 lst 引用的列表对象
print(f"函数内 - 修改后: lst = {lst}, id(lst) = {id(lst)}")
my_list = [1, 2, 3]
print(f"函数外 - 调用前: my_list = {my_list}, id(my_list) = {id(my_list)}")
# my_list 的引用被传递给 lst
modify_mutable(my_list)
print(f"函数外 - 调用后: my_list = {my_list}, id(my_list) = {id(my_list)}")
# 输出:
# 函数外 - 调用前: my_list = [1, 2, 3], id(my_list) = 2323455663976
# 函数内 - 修改前: lst = [1, 2, 3], id(lst) = 2323455663976
# 函数内 - 修改后: lst = [1, 2, 3, 99], id(lst) = 2323455663976
# 函数外 - 调用后: my_list = [1, 2, 3, 99], id(my_list) = 2323455663976 # my_list 被改变了
3 避免意外修改:创建副本
如果你想在函数内部使用可变对象,但又不希望修改外部的原始对象,你需要传递一个副本。
def safe_function(lst):
# 创建一个列表的副本,这样操作的就是新对象了
local_list = lst.copy() # 或者 lst[:]
local_list.append(99)
print(f"函数内: local_list = {local_list}")
my_list = [1, 2, 3]
safe_function(my_list)
print(f"函数外: my_list = {my_list}") # my_list 保持不变
# 输出:
# 函数内: local_list = [1, 2, 3, 99]
# 函数外: my_list = [1, 2, 3]
浅拷贝与深拷贝
当你确实需要复制一个对象时,需要区分浅拷贝和深拷贝。
1 浅拷贝
copy.copy() 或者 list.copy() / dict.copy() 等方法创建的是浅拷贝。
- 对于不可变对象,浅拷贝和直接赋值效果一样,都是引用同一个对象。
- 对于可变对象:
- 它会创建一个新的容器对象(比如一个新的列表)。
- 这个新容器中的元素仍然是引用了原来对象中的元素。
# 示例 4: 浅拷贝
original_list = [1, [2, 3], 4]
# 创建浅拷贝
shallow_copy_list = original_list.copy()
print(f"original_list: {original_list}, id: {id(original_list)}")
print(f"shallow_copy_list: {shallow_copy_list}, id: {id(shallow_copy_list)}")
print(f"original_list[0] is shallow_copy_list[0]: {original_list[0] is shallow_copy_list[0]}") # True (不可变元素)
print(f"original_list[1] is shallow_copy_list[1]: {original_list[1] is shallow_copy_list[1]}") # True (可变元素)
# 修改浅拷贝中的不可变元素
shallow_copy_list[0] = 100
print("\n修改不可变元素后:")
print(f"original_list: {original_list}") # 不受影响
print(f"shallow_copy_list: {shallow_copy_list}")
# 修改浅拷贝中的可变元素
shallow_copy_list[1].append(99)
print("\n修改可变元素后:")
print(f"original_list: {original_list}") # 受到影响!因为内部列表是同一个
print(f"shallow_copy_list: {shallow_copy_list}")
# 输出:
# original_list: [1, [2, 3], 4], id: 2323455664120
# shallow_copy_list: [1, [2, 3], 4], id: 2323455664152
# original_list[0] is shallow_copy_list[0]: True
# original_list[1] is shallow_copy_list[1]: True
# 修改不可变元素后:
# original_list: [1, [2, 3], 4]
# shallow_copy_list: [100, [2, 3], 4]
# 修改可变元素后:
# original_list: [1, [2, 3, 99], 4]
# shallow_copy_list: [100, [2, 3, 99], 4]
2 深拷贝
copy.deepcopy() 创建的是深拷贝。
它会递归地创建所有嵌套对象的副本,确保新对象和原对象没有任何共享的部分,修改深拷贝后的任何部分都不会影响原对象。
# 示例 5: 深拷贝
import copy
original_list = [1, [2, 3], 4]
# 创建深拷贝
deep_copy_list = copy.deepcopy(original_list)
print(f"original_list: {original_list}")
print(f"deep_copy_list: {deep_copy_list}")
print(f"original_list[1] is deep_copy_list[1]: {original_list[1] is deep_copy_list[1]}") # False
# 修改深拷贝中的可变元素
deep_copy_list[1].append(99)
print("\n修改可变元素后:")
print(f"original_list: {original_list}") # 不受影响!
print(f"deep_copy_list: {deep_copy_list}")
# 输出:
# original_list: [1, [2, 3], 4]
# deep_copy_list: [1, [2, 3], 4]
# original_list[1] is deep_copy_list[1]: False
# 修改可变元素后:
# original_list: [1, [2, 3], 4]
# deep_copy_list: [1, [2, 3, 99], 4]
| 概念 | 解释 | 关键点 |
|---|---|---|
| 引用 | 变量名是对象的“标签”,引用是标签与对象内存地址的关联。 | a = 10,a 是一个引用。 |
| 不可变对象 | 不能改变,修改操作会创建新对象。 | int, str, tuple,函数内修改不影响外部变量。 |
| 可变对象 | 可以改变,修改操作在原对象上进行。 | list, dict, set,函数内修改会影响外部变量。 |
| 函数传参 | 传的是对象的引用(地址)。 | 传不可变对象:安全,传可变对象:有副作用,需小心。 |
| 浅拷贝 | 创建新容器,但容器内的元素仍是原对象的引用。 | 修改嵌套的可变元素会影响原对象。 |
| 深拷贝 | 递归创建所有嵌套对象的完全独立副本。 | 新旧对象完全独立,互不影响。 |
理解引用是 Python 编程的基石,当你遇到一个变量在函数调用后“意外”改变,或者当你想要复制一个列表却发现只复制了第一层时,问题通常都出在对引用、可变/不可变对象以及拷贝方式的理解上。
