Python中is运算符是什么意思

宋时安
宋时安
发布时间:2026-10-20 | 3人浏览过
原创
在 Python 中,is 是一个身份比较运算符(Identity Operator),用于判断两个变量是否引用内存中的同一个底层对象。换句话说,is 比较的是两个对象的唯一内存地址(对应内置函数 id() 的返回值),只有当两个变量绑定的物理地址完全一致时才会返回 True。理解 is 的关键,在于将其与用于判断数据内容或数值是否相等的相等运算符 == 明确区分开来。

一、is 运算符的底层本质与运作机制

在 Python 的对象模型中,一切数据皆为对象。每一个对象被解释器分配到堆内存后,都具备三个核心属性:

  • 类型(Type):决定该对象可以存储什么格式的数据、支持哪些操作,通过 type(obj) 查看。
  • 值(Value):对象所承载的具体业务数据,例如整数 100 或列表 [1, 2, 3]。
  • 身份标识(Identity):对象在生命周期内的唯一物理内存地址,通过内置函数 id(obj) 可以获取其整数表示(在标准 CPython 实现中,它就是该对象在 C 结构体中的指针地址)。

当执行表达式 a is b 时,Python 解释器在底层执行的逻辑实际上等价于直接比较两者的内存地址:

# is 运算在逻辑上等价于比对两者内存指针
a is b  <==>  id(a) == id(b)

因为 is 仅仅是提取并比较两个指针的数值,它绝不深入对象的内部结构,也不解析数据内容。下面的完整代码示例直观地展示了这种机制:

# 环境:Python 3.x 交互式终端或脚本环境
# 创建两个内容完全相同的列表对象
list_a = [1, 2, 3]
list_b = [1, 2, 3]
list_c = list_a  # 将 list_a 赋值给 list_c,仅传递对象引用

# 查看各个变量所指向的内存地址(示例地址值每次运行会变)
print("list_a 的内存地址:", id(list_a))  # 示例输出: 140726190821632
print("list_b 的内存地址:", id(list_b))  # 示例输出: 140726190822464
print("list_c 的内存地址:", id(list_c))  # 示例输出: 140726190821632

# 比较同一性 (is) 与相等性 (==)
print(list_a == list_b)  # 示例输出: True  (内容完全一样)
print(list_a is list_b)  # 示例输出: False (内存地址不同,是两个独立的列表)
print(list_a is list_c)  # 示例输出: True  (指向同一块堆内存)

在上述代码中,list_a 和 list_b 虽然具有相同的三个整数元素,但方括号语法分别在堆内存的不同地址开辟了两块独立的存储空间。因此 list_a is list_b 返回 False。而赋值语句 list_c = list_a 并没有复制列表,只是让 list_c 引用了现有的同一个列表对象,所以 list_a is list_c 返回 True。

Python中is运算符是什么意思
图1:Python 对象内存模型与 is 运算符判定机制原理图示

二、is 与 == 的核心区别与多维对比

虽然 is 和 == 都可以写在 if 条件语句中作为比较表达式,但它们代表完全不同的语义:

1. 比较目的不同(同一性 vs 等价性)

is 用于确认“是否为同一实体”(Identity),而 == 用于确认“数据内容是否相等”(Equality)。例如两个孪生兄弟长相、身高、身份证上的姓氏可能完全相同(== 为真),但他们依然是两个独立的自然人个体(is 为假)。

2. 底层调用与可重载性

== 操作符在 Python 中会触发对象的特殊方法 __eq__()。这意味着用户自定义类可以自由重写相等逻辑。例如在定义一个 User 类时,可以指定只要两个实例的 user_id 相同,user_a == user_b 就返回 True。

相反,is 是内置语言级别的操作符,完全无法被重写或重载。没有任何类能够改变 is 的判定行为,它始终如一地只比较内存地址,因此具备绝对的一致性与安全性。

3. 执行速度与时间复杂度

因为 is 仅仅是两个 C 语言指针(整型)的相等比较,其耗时恒定为最底层的 $O(1)$,不需要进出 Python 函数帧栈。而 == 则需要分发到对应的 __eq__() 方法,在比较大型列表、集合或嵌套字典时,还需要逐层深入递归对比每一个元素,开销随着数据量增加而线性增长。

Python中is运算符是什么意思
图2:is 运算符与 == 运算符的核心差异多维对比

三、容易引起误解的现象:小整数池与字符串驻留

很多初学者在使用 Python 交互式命令行时,经常会遇到以下令人困惑的现象:

# 在 Python 交互式终端 (REPL) 中测试:
>>> a = 256
>>> b = 256
>>> a is b
True

>>> x = 257
>>> y = 257
>>> x is y
False

同样是赋值相等的整数字面量,为什么 256 进行 is 比较得到 True,而 257 却得到 False?这一现象极易让人误以为 is 可以用来做小数值的比较,其实这是对解释器内部优化机制的误读。

1. CPython 的小整数对象池(Small Integer Cache)

在日常程序运行中,像 0、1、-1、10 这样的小整数被极其频繁地创建和销毁。为了避免频繁向操作系统申请分配堆内存,CPython 解释器在初始化启动时,就会在内存中预先创建好一个闭区间 [-5, 256] 的整数对象池。

当代码中需要使用这个范围内的任意整数时,Python 直接返回池中现成的对象引用。因此,a 和 b 被赋予 256 时,它们实际上引用了全局唯一的同一个预置对象,id(a) == id(b) 自然为真。而当数值超出该范围(如 257)时,解释器会每次单独为新对象分配内存,因此 x is y 返回 False。

2. 字符串驻留(String Interning)与编译期常量折叠

类似地,对于简短且由下划线、字母和数字组成的无空格字符串(如 "hello" 或合法的 Python 标识符),CPython 会尝试进行“驻留”(Interning),共用同一份字符串实例。此外,当在同一个 .py 脚本或函数代码块中编写 x = 1000; y = 1000 时,Python 编译器在编译当前代码块(Code Block)的常量表(co_consts)时可能会自动进行常量去重,使得脚本执行时即使超过 256 也会碰巧返回 True。

避坑警告:严禁依赖底层驻留优化编写业务逻辑

小整数池、字符串驻留和代码块常量优化,纯粹是 CPython 解释器的内部实现细节。不同平台(如 PyPy、Jython)、不同 Python 次要版本、甚至同一代码是在交互式命令行还是函数体内部运行,其优化策略都有所不同。

从 Python 3.8 开始,如果直接将 is 用于字面量比较(例如 if x is 100:),解释器会在编译阶段明确输出语法警告:SyntaxWarning: "is" with a literal. Did you mean "=="?。比较数值大小时,务必始终使用 ==、<、> 等关系运算符。

Python中is运算符是什么意思
图3:Python 变量比较决策流程图(何时使用 is 与 ==)

四、is 运算符的标准应用场景与 PEP 8 规范

明确了上述边界之后,is 在实际工程开发中的标准应用场景主要集中在以下几类:

1. 检查变量是否为 None(最核心、最普遍的用法)

在 Python 中,None 是单例类 NoneType 的唯一实例。根据官方代码风格规范 PEP 8 的明确要求:

与像 None 这样的单例进行比较时,必须使用 is 或 is not,永远不要使用相等操作符 ==。

遵循这一规范的原因有两点:

  1. 执行效率更高:is None 是原子级的指针比较,不需要进入对象的方法调用流程。
  2. 防御自定义类的恶意重载:如果某个自定义类重写了 __eq__() 方法并不当地返回了 True,使用 obj == None 会得到错误的判定结果,而 obj is None 则不受任何干扰:
class BrokenClass:
    def __eq__(self, other):
        return True  # 无论和谁比都返回 True

broken_obj = BrokenClass()

# 错误用法:被 __eq__ 误导
print(broken_obj == None)  # 示例输出: True (逻辑出现严重偏差!)

# 正确用法:基于物理内存真实判定
print(broken_obj is None)  # 示例输出: False (安全准确)

2. 语法配套的否定运算符:is not

当需要判断某个变量不为 None 时,Python 提供了语义自然的专属操作符 is not。请优先编写 if val is not None:,而不要写成略显拗口的 if not (val is None):。

3. 定义与识别自定义哨兵对象(Sentinel Objects)

在编写函数或缓存类时,有时需要区分“调用方没有传递参数”与“调用方主动传递了 None”这两种截然不同的业务状态。此时最标准的设计模式就是利用 object() 实例化一个全局唯一的哨兵对象:

# 创建一个仅用于占位识别的单例哨兵
_SENTINEL = object()

def get_config(key, default=_SENTINEL):
    value = database.find(key)
    if value is _SENTINEL:
        # 严格确认该配置键在数据库中根本不存在
        return default
    # 即便配置值本身就是 None,也能正常返回而不被当作缺失
    return value

因为 object() 每次调用都会在堆内存中生成一个独一无二的新对象,只有通过 is _SENTINEL 才能精确匹配,任何外部传入的数据都不可能与它共享同一个 id。

Python中is运算符是什么意思
图4:Python is 运算符核心要点速查与总结卡片

五、总结与快速自查清单

归纳全文,掌握 Python 的 is 运算符只需牢记以下三个核心判断原则:

  • 查内存用 is:核心用于确认两个变量引用的对象物理实体是否唯一,主要用于 None 判断、布尔单例校验以及自定义哨兵对象识别。
  • 比内容用 ==:无论是数字的大小判定、文本的内容比对,还是列表、字典、元组等容器的元素核对,只要关注的是“数据值”,必须老老实实用 ==。
  • 绝不拿 is 比字面量:永远不要写 val is 0、status is "active" 或 arr is []。遵守 PEP 8 标准既能确保代码在不同 Python 解释器和优化参数下运行的一致性,也能消除潜在的偶发性 Bug。
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表