Python id() 函数(长文讲解)

Python id() 函数:理解变量与内存的“身份证”

你有没有想过,当你在 Python 中定义一个变量时,它到底“存在”在哪里?这个变量和另一个变量之间,是完全独立的吗?其实,每个变量在内存中都有一个独一无二的“身份标识”——这就是 Python 的 id() 函数要告诉我们的。

id() 函数返回的是对象在内存中的唯一地址,就像每个人的身份证号码一样。它不是变量名,也不是值,而是对象在内存中“位置”的唯一编号。这个编号是动态的,每次程序运行都会变化,但同一时刻,同一个对象的 id 值永远不变。

理解 id() 不仅能帮你搞懂变量赋值的本质,还能避免在写代码时掉进“引用传递”的坑里。接下来,我们就一步步揭开它的面纱。


什么是 Python id() 函数?

id() 是 Python 内置函数之一,用于获取对象在内存中的地址。它的语法非常简单:

id(object)

参数 object 是你想要查询其内存地址的对象。返回值是一个整数,代表该对象在内存中的唯一标识符。

举个例子:

a = 100
print(id(a))

输出可能类似:

140234567890123

这个数字就是变量 a 所指向的整数对象在内存中的“位置编号”。下次运行程序时,这个编号可能会变,但只要程序运行期间,同一个对象的 id 值就不会变。

📌 重要提示id() 返回的是内存地址,不是变量名,也不是值。它反映的是对象的“存在位置”。


数字与字符串的不可变性与 id 变化

Python 中,数字和字符串属于不可变类型(immutable),这意味着一旦创建,就不能被修改。每次对它们进行“修改”操作,实际上都会创建一个新的对象。

来看一个例子:

x = 100
y = 100

print(id(x))  # 输出:140234567890123
print(id(y))  # 输出:140234567890123

你可能会惊讶:xyid 一样?这是因为 Python 为了优化性能,对小整数(通常在 -5 到 256 之间)会进行缓存复用。所以 xy 实际上指向同一个对象。

但当数字超出这个范围时,情况就变了:

a = 1000
b = 1000

print(id(a))  # 输出:140234567891000
print(id(b))  # 输出:140234567891100

你会发现 id 不同了。因为 Python 不再复用大整数对象,而是为每个赋值创建新的对象。

字符串也类似:

s1 = "hello"
s2 = "hello"

print(id(s1))  # 输出:140234567892000
print(id(s2))  # 输出:140234567892000

这里 s1s2id 相同,是因为 Python 对短字符串也做了缓存优化。但如果你用拼接方式创建:

s3 = "hel" + "lo"
s4 = "hello"

print(id(s3))  # 输出:140234567893000
print(id(s4))  # 输出:140234567892000

即使内容一样,id 也不同了。因为拼接是运行时操作,不会触发字符串驻留(interning)机制。

🧠 小贴士:理解不可变性 + id() 可以帮助你判断“两个变量是否指向同一个对象”,而不是“值是否相等”。


列表与字典:可变对象的 id 特性

与数字和字符串不同,列表(list)和字典(dict)是可变对象(mutable)。这意味着你可以修改它们的内容,而无需创建新对象。

但关键点在于:同一个列表对象,即使内容改变,其 id 不变。

my_list = [1, 2, 3]
print(id(my_list))  # 输出:140234567894000

my_list.append(4)
print(id(my_list))  # 输出:140234567894000(不变!)

这说明,append() 方法是在原对象上修改,没有创建新对象。id 保持一致,证明它还是“同一个身份证”。

但如果重新赋值,情况就不同了:

my_list = [1, 2, 3]
print(id(my_list))  # 输出:140234567895000

my_list = [4, 5, 6]  # 重新赋值,创建新对象
print(id(my_list))  # 输出:140234567896000(变了!)

这里 my_list 原来指向的列表对象被“抛弃”,新列表被创建,id 也变了。

总结:可变对象的 id 不变,表示它在原地被修改;id 变了,说明变量指向了新对象。


变量赋值的本质:引用 vs 值拷贝

很多人初学 Python 时,会误以为变量赋值是“复制值”。但其实,Python 的变量赋值是“引用赋值”——变量名只是指向对象的“标签”。

来看一个经典例子:

a = [1, 2, 3]
b = a  # b 指向 a 所指向的对象

print(id(a))  # 输出:140234567897000
print(id(b))  # 输出:140234567897000(相同!)

b.append(4)
print(a)  # 输出:[1, 2, 3, 4] —— a 也被改变了!

为什么会这样?因为 ab 指向的是同一个列表对象。修改 b 就等于修改了这个对象本身,a 也跟着变化。

这正是 id() 的价值所在:它能帮你判断两个变量是否“共享”同一个对象。

如何避免这种“意外修改”?

如果你希望 ba 的独立副本,必须显式复制:

a = [1, 2, 3]
b = a.copy()  # 创建新列表,内容相同但 id 不同

print(id(a))  # 输出:140234567898000
print(id(b))  # 输出:140234567899000(不同!)

b.append(4)
print(a)  # 输出:[1, 2, 3] —— a 没变!

通过 id() 比较,你能清楚看到两个变量是否独立。


使用 id() 的实际场景与注意事项

id() 不常用于日常业务逻辑,但它在调试、性能分析和理解底层机制时非常有用。

场景一:调试对象是否为同一个

当你在处理复杂数据结构时,可能需要判断两个变量是否引用同一个对象。

def process_data(data):
    # 检查是否是同一个对象
    if id(data) == id(some_global_data):
        print("正在处理全局数据")
    else:
        print("使用的是副本")

data = [1, 2, 3]
process_data(data)

场景二:避免不必要的深拷贝

如果你知道两个变量 id 相同,就不需要再执行 copy.deepcopy(),节省资源。

场景三:理解缓存机制

Python 的整数缓存、字符串驻留等优化机制,都可通过 id() 观察到。例如:

a = 100
b = 100
print(id(a) == id(b))  # True

c = 1000
d = 1000
print(id(c) == id(d))  # False(通常)

这说明 Python 并非对所有整数都缓存。


常见误区与陷阱

误区 1:id() 可以用来判断值是否相等

错误写法:

a = 100
b = 100
if id(a) == id(b):  # ❌ 不推荐!
    print("值相等")

正确做法是用 ==

if a == b:  # ✅ 判断值是否相等
    print("值相等")

id() 用于判断“是否同一个对象”,不是“值是否相等”。

误区 2:id() 是固定的,可以长期使用

错误认知:id 是永久不变的。

事实:id 是程序运行期间的内存地址,每次运行程序都会变化。不能依赖 id 做持久化存储或比较


总结与建议

Python id() 函数 是理解 Python 内存模型和变量机制的关键工具。它虽然不常出现在业务代码中,但在调试、性能优化、理解引用机制时不可或缺。

  • id() 返回对象在内存中的唯一地址;
  • 不可变对象(数字、字符串)在某些情况下会复用,id 相同;
  • 可变对象(列表、字典)修改内容时 id 不变,说明是原地修改;
  • 变量赋值是“引用赋值”,id 相同意味着共享对象;
  • 使用 id() 能帮助你判断对象是否“同一个”,但不要用于判断值相等。

学习建议:在学习 Python 变量机制时,养成习惯:遇到赋值或修改操作,先用 id() 打印一下,看看对象是否被复用或创建新实例。

掌握 id(),你就不再只是“写代码”,而是真正“理解代码在计算机中如何运行”。这正是从初级开发者迈向中级、高级开发者的分水岭。

Python id() 函数,看似简单,实则深藏玄机。愿你在每一次 print(id(x)) 的输出中,都能多一点对 Python 的敬畏与理解。