Python id() 函数

Python id() 语法

id() 是 Python 的一个内置函数,它用于获取某个对象的内存地址。其中,Python 每个对象都会有自己的唯一 ID(即内存地址)。

语法:

id(object)

说明:

id() 接收单个参数。

  • object(必选):是任意的 Python 对象,比如列表、元组、类等。

id() 函数会返回一个整数,该整数代表的就是该对象的内存地址。由于每个对象在内存中都有唯一的地址,因此 id() 函数的返回值也是唯一的。

对于 id() 函数,小伙伴们要清楚以下几点。

  • 对象的身份是唯一的,并且在对象的整个生命周期中保持不变。也就是说,对象一旦被创建,它的 id() 值就不会改变。
  • 两个具有相同值的对象可能拥有不同的 id(),这取决于它们是否是同一个内存中的对象。
  • CPython(Python 的主要实现)的 id() 返回的是对象在内存中的地址。但请注意,不同的 Python 实现(如 Jython、IronPython)可能以不同的方式实现 id(),所以不应该依赖于这个身份来推断内存布局。
  • id() 主要用于调试和理解 Python 的对象模型,不建议在生产环境中频繁使用 id() 来判断对象的相等性,而应该使用 is 运算符(判断身份相等)或 == 运算符(判断值相等)。

注意: 由于 Python 的垃圾回收机制,当对象不再被引用时,其内存地址可能会被重用。因此,id() 函数的返回值可能会随着时间的推移而变化,特别是对于短期存在的对象。

Python id() 摘要

使用频率
官方文档 查看
相关函数 hash()

Python id() 示例

接下来,我们通过几个简单的例子来讲解一下 Python id() 函数是如何使用的。

示例 1:id() 与不可变对象的缓存

a = 100
b = 100
c = 100.0

print(id(a))
print(id(b))
print(id(c))

运行结果如下。

1954110094736
1954110094736
1954110094896

分析:

在 CPython 解释器中,为了提高性能,-5 到 256 范围内的整数对象会被预先创建并缓存。

当 a = 100 和 b = 100 被执行时,它们都指向内存中同一个预先存在的整数对象。因此,id(a) 和 id(b) 是相同的。

c 是一个浮点数 (100.0) 对象。浮点数一般不会被缓存,它被创建为一个新的对象。因此,id(c) 与 id(a) 和 id(b) 不同。

示例 2:id() 判断 “引用类型”

a = [1, 2, 3]
b = [1, 2, 3]

print(id(a))
print(id(b))

运行结果如下。

1937644265152
1937644340672

分析:

虽然 a 和 b 看起来是一样的,但它们都属于引用类型,本质上是两个不同的对象,所以 id 也不同。

示例 3:id() 的更多使用

a = [1, 2, 3]
b = a

print(id(a))
print(id(b))

运行结果如下。

2816681909952
2816681909952

分析:

这里执行的是 b = a,a 和 b 本质上是同一个对象,因此它们的 id 也相同。

深入理解 id()

想要更深入地掌握 id() 函数,小伙伴们要认真理解以下几个方面。

1. id() 与 is 运算符的关系

id() 函数与 is 运算符紧密相关。实际上,表达式 a is b 等价于 id(a) == id(b)。两者都可以用于判断两个变量是否引用了内存中的同一个对象实例。

示例 4:id() vs is

a = [1, 2]
b = a            # b 引用了与 a 相同的列表对象
c = [1, 2]       # c 是一个新创建的列表对象,即使内容相同

print(f'a 的 id: {id(a)}')
print(f'b 的 id: {id(b)}')
print(f'c 的 id: {id(c)}')

print(f'a is b: {a is b}')     # 比较身份:a 和 b 指向同一个对象
print(f'a is c: {a is c}')     # 比较身份:a 和 c 指向不同对象
print(f'a == c: {a == c}')     # 比较值:a 和 c 的值相同

运行结果如下。

a 的 id: 2332443762752
b 的 id: 2332443762752
c 的 id: 2332443912704
a is b: True
a is c: False
a == c: True

分析:

当 a 和 b 引用同一个列表对象时,它们的 id() 相同,a is b 为 True。而 c 尽管与 a 具有相同的内容,但它是一个独立的列表对象,因此 id(a) 和 id(c) 不同,a is c 为 False。

2. 自定义对象的 id()

每个自定义类的实例在内存中都是一个独立的、唯一的对象,即使它们的属性值完全相同。因此,每个实例都会有自己独特的 id()。

示例 5:自定义对象的 id()

class User:
    def __init__(self, name):
        self.name = name

u1 = User('Jack')
u2 = User('Jack')
u3 = u1     # u3 引用 u1

print(id(u1))
print(id(u2))
print(id(u3))

print(id(u1) == id(u2))   # 即使属性相同,也是不同对象
print(id(u1) == id(u3))   # 引用同一对象

运行结果如下。

2267153460800
2267155418640
2267153460800
False
True

分析:

u1 和 u2 虽然拥有相同的 name 属性,但它们是两个独立的 User 对象,因此它们的 id() 值不同。而 u3 = u1 使得 u3 指向了与 u1 相同的对象,所以它们的 id() 是相同的。

3. 不可变容器的特殊情况

元组(tuple)这样的不可变容器,它们本身的 id() 在创建后是不会改变的。但是,如果元组中包含可变对象(如列表),并且这个可变对象的内容发生了变化,虽然元组本身的 id() 不变,但其内部引用的可变对象的 id() 仍然是它原本的,并且其内容会反映变化。这可能会导致一些关于 “不变性” 的误解。

示例 6:元组的特殊情况

nums = [1, 2]
t1 = (nums, 3)        # 元组 t1 包含了对 nums 的引用
t2 = ([1, 2], 3)      # 元组 t2 包含了一个新的列表对象

# 修改前
print(id(nums))
print(id(t1))
print(id(t2))
print(id(t1) == id(t2))

# 修改元组
print(id(t1[0]))
nums.append(4)     # 修改 nums 的内容
print(id(t1[0]))
print(id(nums))

运行结果如下。

2574634410048
2574635951744
2574635951808
False
2574634410048
2574634410048
2574634410048

分析:

虽然 t1 和 t2 在创建时包含的列表内容相同,但由于它们是不同的元组对象,所以它们的 id() 不同。

关键在于,t1 本身是不可变的,它的 id() 不会变。但是,t1 内部存储的是对 nums 这个可变对象的引用。当 nums 的内容被修改时(通过 nums.append(4)),t1 中对应的元素所引用的对象的内容也随之改变,而 nums 本身的 id() 并没有变。

从这个例子可以清楚地知道,元组的 “不可变” 指的是其元素引用的不可变,而不是其元素所指向对象内容的不可变。

常见问题

1. id() 的返回值是否是绝对唯一的?

在对象存活期间,id() 返回值是唯一的。但对象销毁后,其 id 值可能被新对象复用。

2. 如何判断两个变量是否指向同一对象?

我们可以使用 2 种方式:(1)使用 is 运算符;(2)直接比较 id() 返回值。

3. id() 的值是否与内存地址直接对应?

在 Cpython 中,id() 返回对象的内存地址。但在其他解释器(如 PyPy)中,可能会不一样。

上一篇: format()

下一篇: hash()

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号