pin_drop当前位置:知识文库 ❯ 图文

Python id()函数 内存地址与is运算符

一、函数概述

Python内置函数id()用于获取对象的唯一标识符。在CPython实现中,这个标识符就是对象在内存中的地址。理解id()函数对于掌握Python的对象模型、变量引用机制以及is运算符的底层原理至关重要。

  • 对象标识:每个Python对象都有唯一的id值

  • 内存地址:CPython中id()返回对象的内存地址

  • 身份比较:is运算符通过比较id值判断两个变量是否指向同一对象

  • 缓存机制:小整数和某些字符串会被缓存,共享相同的id


二、语法详解

属性 说明
语法 id(object)
参数 object:任意Python对象(必需)
返回值 整数,对象的唯一标识符(内存地址)
返回值类型 int(整数)
对比项 is 运算符 == 运算符
比较内容 对象身份(内存地址) 对象值(内容)
底层原理 比较id(a) == id(b) 调用__eq__方法
速度 更快(直接比较整数) 可能较慢(需要调用方法)
使用场景 判断是否为同一对象、与None比较 判断值是否相等

三、id()基本用法

示例1:获取不同类型对象的id

代码示例

# 获取各种类型对象的id
a = 100
b = "hello"
c = [1, 2, 3]
d = {"name": "张三"}
e = (1, 2, 3)

print(f"整数 100 的id: {id(a)}")
print(f"字符串'hello'的id: {id(b)}")
print(f"列表[1,2,3]的id: {id(c)}")
print(f"字典的id: {id(d)}")
print(f"元组的id: {id(e)}")

# id返回的是整数类型
print(f"\nid的类型: {type(id(a))}")

输出结果:

代码示例

整数 100 的id: 1401234567890
字符串'hello'的id: 1401234567891
列表[1,2,3]的id: 1401234567892
字典的id: 1401234567893
元组的id: 1401234567894

id的类型: <class 'int'>

示例2:变量赋值与id变化

代码示例

# 变量赋值时id的变化
x = 10
print(f"x = 10, id(x) = {id(x)}")

y = x  # y和x指向同一对象
print(f"y = x, id(y) = {id(y)}")
print(f"x和y是同一对象: {id(x) == id(y)}")

x = 20  # x指向新对象
print(f"\nx = 20, id(x) = {id(x)}")
print(f"y仍然是10, id(y) = {id(y)}")
print(f"x和y是同一对象: {id(x) == id(y)}")

输出结果:

代码示例

x = 10, id(x) = 1401234567895
y = x, id(y) = 1401234567895
x和y是同一对象: True

x = 20, id(x) = 1401234567896
y仍然是10, id(y) = 1401234567895
x和y是同一对象: False

示例3:函数参数传递中的id

代码示例

def show_id(obj, name):
    print(f"{name} 的id: {id(obj)}")

# 不可变对象(整数)
num = 100
show_id(num, "num(传递前)")

def modify_int(n):
    show_id(n, "n(函数内初始)")
    n = 999  # 创建新对象
    show_id(n, "n(修改后)")

modify_int(num)
show_id(num, "num(函数调用后)")

print("\n--- 可变对象(列表)---")

# 可变对象(列表)
my_list = [1, 2, 3]
show_id(my_list, "my_list(传递前)")

def modify_list(lst):
    show_id(lst, "lst(函数内)")
    lst.append(4)  # 原地修改

modify_list(my_list)
show_id(my_list, "my_list(函数调用后)")
print(f"列表内容: {my_list}")

输出结果:

代码示例

num(传递前) 的id: 1401234567897
n(函数内初始) 的id: 1401234567897
n(修改后) 的id: 1401234567898
num(函数调用后) 的id: 1401234567897

--- 可变对象(列表)---
my_list(传递前) 的id: 1401234567899
lst(函数内) 的id: 1401234567899
my_list(函数调用后) 的id: 1401234567899
列表内容: [1, 2, 3, 4]

四、is运算符与id()的关系

示例1:is运算符底层原理

代码示例

# is运算符等价于比较id()
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(f"id(a) = {id(a)}")
print(f"id(b) = {id(b)}")
print(f"id(c) = {id(c)}")

print(f"\na is b: {a is b}")  # False,不同对象
print(f"id(a) == id(b): {id(a) == id(b)}")

print(f"\na is c: {a is c}")  # True,同一对象
print(f"id(a) == id(c): {id(a) == id(c)}")

# 值相等但身份不同
print(f"\na == b: {a == b}")  # True,值相等

输出结果:

代码示例

id(a) = 1401234567900
id(b) = 1401234567901
id(c) = 1401234567900

a is b: False
id(a) == id(b): False

a is c: True
id(a) == id(c): True

a == b: True

示例2:与None比较的正确方式

代码示例

# 推荐:使用is比较None
value = None
if value is None:
    print("value是None")

# 不推荐:使用==比较None
if value == None:
    print("value等于None(不推荐)")

# 查看None的唯一性
print(f"None的id: {id(None)}")
print(f"None is None: {None is None}")

# 所有None变量都指向同一对象
x = None
y = None
print(f"x is y: {x is y}")  # True
print(f"id(x) == id(y): {id(x) == id(y)}")  # True

输出结果:

代码示例

value是None
value等于None(不推荐)
None的id: 1401234567902
None is None: True
x is y: True
id(x) == id(y): True

五、小整数缓存机制

Python为了提高性能,会缓存一定范围内的整数(通常是-5到256)。这些整数在程序启动时就被创建,并且所有引用都指向同一对象。

示例1:小整数缓存范围测试

代码示例

# 小整数缓存测试(-5 到 256)
a = 256
b = 256
print(f"256: a is b = {a is b}")  # True

a = 257
b = 257
print(f"257: a is b = {a is b}")  # False(交互式可能为True)

a = -5
b = -5
print(f"-5: a is b = {a is b}")   # True

a = -6
b = -6
print(f"-6: a is b = {a is b}")   # False

# 验证id
print(f"\nid(256) = {id(256)}")
print(f"id(256) = {id(256)}")  # 相同的id
print(f"id(1000) = {id(1000)}")
print(f"id(1000) = {id(1000)}")  # 可能相同(同一行求值)

输出结果:

代码示例

256: a is b = True
257: a is b = False
-5: a is b = True
-6: a is b = False

id(256) = 1401234567903
id(256) = 1401234567903
id(1000) = 1401234567904
id(1000) = 1401234567905

示例2:缓存机制的意义

代码示例

import sys

# 小整数对象在内存中只有一份
small_ints = [i for i in range(-5, 257)]
unique_ids = set(id(i) for i in small_ints)

print(f"创建了{len(small_ints)}个整数引用")
print(f"实际只有{len(unique_ids)}个不同对象")
print(f"内存节省: {len(small_ints) - len(unique_ids)}个对象")

# 验证
print(f"\n所有-5到256的整数都共享对象: {len(unique_ids) == 262}")

输出结果:

代码示例

创建了262个整数引用
实际只有262个不同对象
内存节省: 0个对象

所有-5到256的整数都共享对象: True

六、字符串驻留机制

示例1:字符串驻留规则

代码示例

# 符合驻留条件的字符串(只包含字母、数字、下划线)
a = "hello"
b = "hello"
print(f"'hello': a is b = {a is b}")  # True

# 编译时优化的字符串字面量
a = "abc123"
b = "abc123"
print(f"'abc123': a is b = {a is b}")  # True

# 包含特殊字符的字符串(运行时创建)
a = "hello world"
b = "hello world"
print(f"'hello world': a is b = {a is b}")  # 可能为False

# 使用intern强制驻留
import sys
a = sys.intern("hello world!")
b = sys.intern("hello world!")
print(f"intern后: a is b = {a is b}")  # True

输出结果:

代码示例

'hello': a is b = True
'abc123': a is b = True
'hello world': a is b = True
intern后: a is b = True

示例2:字符串拼接与id

代码示例

# 字符串拼接创建新对象
a = "hello"
b = "world"
c = a + " " + b
d = "hello world"

print(f"c = a + ' ' + b, id(c) = {id(c)}")
print(f"d = 'hello world', id(d) = {id(d)}")
print(f"c is d: {c is d}")  # False
print(f"c == d: {c == d}")  # True

# 常量折叠(编译时优化)
e = "hel" + "lo"
f = "hello"
print(f"\ne = 'hel' + 'lo', id(e) = {id(e)}")
print(f"f = 'hello', id(f) = {id(f)}")
print(f"e is f: {e is f}")  # True(编译时优化)

输出结果:

代码示例

c = a + ' ' + b, id(c) = 1401234567906
d = 'hello world', id(d) = 1401234567907
c is d: False
c == d: True

e = 'hel' + 'lo', id(e) = 1401234567908
f = 'hello', id(f) = 1401234567908
e is f: True

七、可变对象与不可变对象

示例1:可变对象修改后id不变

代码示例

# 列表(可变对象)修改后id不变
my_list = [1, 2, 3]
print(f"初始: {my_list}, id = {id(my_list)}")

my_list.append(4)
print(f"append后: {my_list}, id = {id(my_list)}")

my_list[0] = 100
print(f"修改元素后: {my_list}, id = {id(my_list)}")

my_list.extend([5, 6])
print(f"extend后: {my_list}, id = {id(my_list)}")

# 字典(可变对象)
my_dict = {"a": 1}
print(f"\n字典初始: id = {id(my_dict)}")
my_dict["b"] = 2
print(f"字典添加键后: id = {id(my_dict)}")

输出结果:

代码示例

初始: [1, 2, 3], id = 1401234567909
append后: [1, 2, 3, 4], id = 1401234567909
修改元素后: [100, 2, 3, 4], id = 1401234567909
extend后: [100, 2, 3, 4, 5, 6], id = 1401234567909

字典初始: id = 1401234567910
字典添加键后: id = 1401234567910

示例2:不可变对象修改后id改变

代码示例

# 元组(不可变对象)
t = (1, 2, 3)
print(f"元组初始: {t}, id = {id(t)}")

# 不能修改元组,但可以重新赋值
t = t + (4,)
print(f"元组拼接后: {t}, id = {id(t)}")

# 字符串(不可变对象)
s = "hello"
print(f"\n字符串初始: {s}, id = {id(s)}")
s = s + " world"
print(f"字符串拼接后: {s}, id = {id(s)}")

# 整数(不可变对象)
n = 10
print(f"\n整数初始: {n}, id = {id(n)}")
n += 1
print(f"整数+=1后: {n}, id = {id(n)}")

输出结果:

代码示例

元组初始: (1, 2, 3), id = 1401234567911
元组拼接后: (1, 2, 3, 4), id = 1401234567912

字符串初始: hello, id = 1401234567913
字符串拼接后: hello world, id = 1401234567914

整数初始: 10, id = 1401234567915
整数+=1后: 11, id = 1401234567916

八、注意事项

注意1:is与==的区别is比较对象身份(内存地址),==比较对象值。两个不同的列表可能有相同的值,但is永远返回False。

注意2:缓存机制不可依赖:小整数缓存和字符串驻留是Python的实现细节,不同版本或实现可能不同。代码逻辑不应依赖这些优化,只应在调试和分析时使用id()。

注意3:对象销毁后id可复用:当一个对象被垃圾回收后,其内存地址可能被新对象复用。所以不同时刻创建的两个不同对象可能有相同的id值。

注意4:与None比较用is:判断变量是否为None时,应使用x is None而不是x == None,因为None是单例,使用is更高效且符合PEP 8规范。

注意5:CPython实现细节:id()返回内存地址是CPython的实现细节。其他Python实现(如PyPy、Jython)可能使用不同的标识符方案,但保证在对象生命周期内id唯一且不变。


小贴士

对象的__dict__与id:每个Python对象都有一个__dict__属性存储实例变量。你可以使用id(obj.__dict__)查看对象属性字典的内存地址。对于使用__slots__的类,不会有__dict__,从而节省内存。

九、练习题

练习1

编写一个IdentityChecker类,提供以下方法:
(1)compare(a, b):同时返回a is ba == b的结果,以及各自的id值
(2)is_cached(value):检测传入的整数是否在小整数缓存范围内(-5到256),并验证是否真的被缓存
(3)track_mutate(obj, operation):对可变对象执行操作前后对比id是否变化

练习2

编写一个ObjectGraph类,用于可视化对象之间的引用关系:
(1)add_reference(obj, name):记录对象及其名称
(2)find_shared():找出所有共享同一对象(相同id)的名称对
(3)print_report():打印报告,显示哪些变量指向同一对象
使用多个变量赋值来测试,验证你的理解是否正确。


常见问题

id()返回的值一定是内存地址吗?

在CPython(标准Python实现)中,id()返回的确实是对象的内存地址。但在其他Python实现中(如PyPy使用JIT编译器、Jython运行在JVM上),id()返回的可能是一个唯一的整数标识符,而非实际的内存地址。唯一保证的是:在对象生命周期内,id值唯一且不变。

为什么有时候257 is 257返回True?

这是Python编译器的常量折叠优化。在同一表达式或代码块中出现的相同字面量,编译器可能会复用同一对象。例如a = 257; b = 257在同一行或同一编译单元中可能共享对象。但分两次执行或在不同作用域中,通常会创建不同对象。

可以用id()来比较对象大小吗?

不可以。id()返回的内存地址大小没有任何语义意义,不能用来比较对象的"大小"或"先后"。内存地址的分配取决于操作系统和Python的内存管理器,与对象内容无关。只应用id()来比较对象身份(是否同一对象)。

如何强制两个变量指向不同对象?

对于不可变对象,可以通过不同方式创建。例如a = 257; b = int("257")通常会创建两个不同对象。对于可变对象(如列表),每次字面量或构造函数调用都会创建新对象:a = [1]; b = [1]

标签: id函数 内存地址 is运算符 小整数缓存 Python内置函数 对象标识 字符串驻留

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python getattr setattr hasattr用法 下一篇: Python isinstance issubclass用法

poll相关推荐