Python基础指南之is与==的区别及使用场景详解

 更新时间:2026年06月14日 09:58:31   作者:星河耀银海  
Python中==一般用来比较值相等,is用来比较对象身份, 本文将深入解析Python中is和==操作符的核心区别及使用场景,文内通过丰富代码示例和生活化类比,帮助开发者理解何时该用==比较值,何时该用is判断对象同一性,避免常见误区

一、开篇:一个让无数Python初学者困惑的问题

先看一段代码,猜猜输出什么:

a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)  # ?
print(a is b)  # ?

如果你的答案是"两个都输出True",那这篇文章就是为你准备的。正确答案是:

print(a == b)  # True  —— 值相等
print(a is b)  # False —— 但不是同一个对象!

再来看另一段让人困惑的代码:

a = 256
b = 256
print(a == b)  # True
print(a is b)  # True —— 咦?这次is也是True?

a = 257
b = 257
print(a == b)  # True
print(a is b)  # False —— 又变成False了?!

这种看似不一致的行为,根源就在于==is的本质区别——一个比较,一个比较身份。今天这篇文章,我就带你深入理解这两个操作符的底层原理和使用场景,让你以后再也不会在这上面踩坑。

二、核心区别:== 比的是值,is 比的是身份

2.1 一句话总结

  • == 比较两个对象的值是否相等(调用 __eq__ 方法)
  • is 比较两个对象是否是同一个对象(比较 id())

用生活中的类比:

  • == 相当于:这两张100元钞票的购买力相同吗?(比价值)
  • is 相当于:这是同一张钞票吗?(比身份)

2.2 用id()理解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)}')

# a is c → True(同一个对象,id相同)
print(f'a is c: {a is c}')          # True
print(f'id(a) == id(c): {id(a) == id(c)}')  # True

# a is b → False(虽然值相同,但id不同)
print(f'a is b: {a is b}')          # False
print(f'id(a) == id(b): {id(a) == id(b)}')  # False

# 但它们的值相等
print(f'a == b: {a == b}')          # True

2.3 图解:值和身份的区别

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

内存中的状态:

  • a ──→ [1, 2, 3]  ←── c
  • b ──→ [1, 2, 3]    (另一个独立的列表)

a is c → True  (指向同一个对象)
a is b → False (指向不同的对象)
a == b → True  (两个对象的值相同)

三、深入理解==

3.1 == 调用的是__eq__方法

当你写a == b时,Python实际上调用的是a.__eq__(b)

# a == b 等价于 a.__eq__(b)

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

# 这两种写法等价
print(a == b)            # True
print(a.__eq__(b))       # True

# 不同类型的对象有不同的__eq__实现
print([1, 2] == [1, 2])        # True——列表逐元素比较
print((1, 2) == (1, 2))        # True——元组逐元素比较
print({'a': 1} == {'a': 1})    # True——字典逐键值比较
print({1, 2} == {2, 1})        # True——集合内容相同,顺序无关

# 自定义类的__eq__
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __eq__(self, other):
        if not isinstance(other, Person):
            return False
        return self.name == other.name and self.age == other.age

p1 = Person('小明', 25)
p2 = Person('小明', 25)
p3 = Person('小红', 30)

print(p1 == p2)  # True——我们定义了__eq__,比较name和age
print(p1 == p3)  # False

3.2 没有自定义__eq__时的默认行为

# 如果类没有定义__eq__,默认继承object的__eq__
# object.__eq__默认比较的就是is(身份)

class Simple:
    def __init__(self, value):
        self.value = value

s1 = Simple(10)
s2 = Simple(10)
s3 = s1

print(s1 == s2)  # False——没有自定义__eq__,默认比较身份
print(s1 == s3)  # True——同一个对象
print(s1 is s2)  # False
print(s1 is s3)  # True

3.3 ==的比较逻辑

Python在执行a == b时的实际流程:

Python的==比较流程:

1. 先尝试调用 a.__eq__(b)

2. 如果返回 NotImplemented,尝试调用 b.__eq__(a)

3. 如果都返回 NotImplemented,回退到比较 id(a) == id(b)(即is)

# 实验:观察NotImplemented的行为
class AlwaysNotImplemented:
    def __eq__(self, other):
        return NotImplemented
class AlwaysTrueEquals:
    def __eq__(self, other):
        return True
a = AlwaysNotImplemented()
b = AlwaysTrueEquals()
# a.__eq__(b)返回NotImplemented,于是尝试b.__eq__(a)
# b.__eq__(a)返回True
print(a == b)  # True

四、深入理解is

4.1 is比较的是对象身份

is操作符不能被重载——它永远比较对象身份

你无法通过定义__eq__或任何特殊方法来改变is的行为

class MyClass:
    def __eq__(self, other):
        return True  # 让==始终返回True
    # 没有__is__方法!is的行为无法改变
obj1 = MyClass()
obj2 = MyClass()
print(obj1 == obj2)  # True——__eq__被重载了
print(obj1 is obj2)  # False——is不受影响,永远比较身份

4.2 is的等价写法

a is b 和 id(a) == id(b) 在效果上等价

但is是原子操作,更高效也更安全

a = [1, 2, 3]
b = a
print(a is b)                    # True
print(id(a) == id(b))           # True

但请注意:不推荐用id() == id()替代is

  • 原因1:id()在极短时间内可能被复用(对象销毁后新对象可能获得相同id)
  • 原因2:is是语法级别的操作,比函数调用更快
  • 原因3:is更符合Python的编码风格

反例——这个极短时间差里可能出问题

temp_a = some_object
temp_b = other_object
result = (id(temp_a) == id(temp_b))  # 不如直接用 temp_a is temp_b

五、Python的整数缓存和字符串驻留

5.1 小整数缓存 [-5, 256]

这是Python初学者最容易困惑的地方。为什么256 is 256是True,257 is 257却是False?

# Python在启动时会预创建-5到256之间的所有整数对象
# 这些整数会被缓存和复用

# 范围内的整数——被缓存
a = 256
b = 256
print(a is b)  # True——两个变量都指向缓存中的同一个256对象

a = -5
b = -5
print(a is b)  # True——同样被缓存

# 范围外的整数——每次创建新对象
a = 257
b = 257
print(a is b)  # False——每次写257都创建新对象

a = -6
b = -6
print(a is b)  # False——-6不在缓存范围内

# 💡 但注意:同一行赋值的相同整数也可能共享
a = 257; b = 257
print(a is b)  # 可能True!因为解释器在同一行可能会优化
# 这是编译器优化行为,不要依赖它

5.2 字符串驻留(String Interning)

# Python对某些字符串也会做驻留(intern)——缓存和复用

# 简单字符串——通常会被驻留
a = "hello"
b = "hello"
print(a is b)  # True——字符串被驻留了

# 包含空格的字符串——也可能被驻留
a = "hello world"
b = "hello world"
print(a is b)  # True——通常也会被驻留

# 但动态拼接的字符串——不一定被驻留
a = "hello"
b = "world"
c = a + b
d = "helloworld"
print(c is d)  # 可能False——动态拼接的不一定被驻留
print(c == d)  # True——值相等

# 用sys.intern()手动驻留
import sys
a = sys.intern("hello world " + "!")
b = sys.intern("hello world " + "!")
print(a is b)  # True——手动驻留后就是同一个对象

# ⚠️ 字符串驻留规则取决于Python实现细节
# 不要在生产代码中依赖字符串is的比较!
# 始终用==来比较字符串的值

5.3 其他类型的缓存行为

# 空元组——被缓存
a = ()
b = ()
print(a is b)  # True——空元组是单例

# 小元组——可能被缓存(取决于实现)
a = (1, 2, 3)
b = (1, 2, 3)
print(a is b)  # 可能True也可能False——不要依赖

# None——单例
a = None
b = None
print(a is b)  # True——None永远是同一个对象

# True和False——单例
a = True
b = True
print(a is b)  # True

# 小列表和字典——不会被缓存
a = []
b = []
print(a is b)  # False——每次创建新列表

六、什么时候用is?

6.1 黄金法则:和None比较时永远用is

# ✅ 推荐——用is比较None
if x is None:
    print('x是None')

if result is not None:
    print('有结果')

# ❌ 不推荐——用==比较None
if x == None:      # 理论上可以,但是...
    print('x是None')

# 为什么is更好?
# 1. None是单例——整个Python进程中只有一个None对象,is是最精确的检查
# 2. is比==更快——不需要调用__eq__方法
# 3. is不会被重载——==可能被对象的__eq__重载,产生意外行为
# 4. PEP 8明确推荐:Comparisons to singletons like None should always be done with is or is not

# 意外行为的例子:
class TrickyNone:
    def __eq__(self, other):
        return True  # 和任何东西比较都返回True

x = TrickyNone()
print(x == None)  # True!——但x显然不是None
print(x is None)  # False——is正确判断

6.2 其他适合用is的场景

# 1. 和True/False比较——通常不需要显式比较
# ✅ 直接用布尔上下文
if some_value:            # 而不是 if some_value is True:
    pass

# 特殊情况下用is(如需要区分True和1)
flag = True
print(flag is True)   # True
print(flag == 1)      # 也是True(因为True==1)

# 2. 检查对象类型
if type(obj) is int:   # 但通常用 isinstance(obj, int) 更好
    pass

# 3. 检查是否是同一个哨兵对象
SENTINEL = object()  # 创建唯一的哨兵对象

def search(data, target, default=SENTINEL):
    result = data.get(target, SENTINEL)
    if result is SENTINEL:  # 用is检查是否返回了默认值
        return '未找到'
    return result

# 4. 检查空序列(空元组是单例)
a = ()
b = ()
print(a is b)  # True——但通常用 len(a) == 0 或 not a 更好

6.3 什么时候不应该用is

# ❌ 不要用is比较数值
a = 1000
b = 1000
if a is b:  # 危险!可能False
    pass

# ❌ 不要用is比较字符串(除非你知道自己在做什么)
name = input("输入名字: ")
if name is "admin":  # 危险!几乎肯定是False
    pass

# ❌ 不要用is比较列表、字典、集合的内容
lst1 = [1, 2, 3]
lst2 = [1, 2, 3]
if lst1 is lst2:  # 危险!只要不是同一个对象就是False
    pass

# ✅ 这些情况用==
if a == b:         # 比较值
if name == "admin":  # 比较字符串
if lst1 == lst2:   # 比较列表

七、实战场景

7.1 哨兵对象的经典用法

# 哨兵对象(Sentinel)——用is来判断"未设置"或"未找到"
# 为什么不用None?因为None可能是合法的返回值

# 创建唯一的哨兵对象
_MISSING = object()
_DELETED = object()

class Cache:
    def __init__(self):
        self._data = {}
    
    def get(self, key, default=_MISSING):
        """获取缓存值。可以区分"值为None"和"键不存在"。"""
        if key in self._data:
            value = self._data[key]
            if value is _DELETED:
                # 键被标记为删除
                if default is _MISSING:
                    raise KeyError(key)
                return default
            return value
        else:
            if default is _MISSING:
                raise KeyError(key)
            return default
    
    def delete(self, key):
        """标记删除(而不是真的删除,保留墓碑)"""
        self._data[key] = _DELETED
    
    def set(self, key, value):
        """设置值——None也是合法的值"""
        self._data[key] = value


# 使用示例
cache = Cache()
cache.set('name', None)  # None是合法值
cache.set('age', 25)
cache.delete('age')

print(cache.get('name'))         # None——合法的存储值
print(cache.get('age', 'N/A'))   # 'N/A'——被删除了
print(cache.get('email', 'N/A')) # 'N/A'——不存在

# 如果没有哨兵对象,怎么区分"值为None"和"键不存在"?
# 用None做默认值就无法区分了!

7.2 单例模式中的is

# 单例模式——全局只有一个实例
# 用is来验证单例特性

class AppConfig:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    
    def __init__(self):
        if self._initialized:
            return
        self.config = {}
        self._initialized = True
    
    def load(self, **kwargs):
        self.config.update(kwargs)


# 验证单例
config1 = AppConfig()
config2 = AppConfig()

print(config1 is config2)  # True——同一个实例
# 整个应用中只有一个AppConfig实例

config1.load(host='localhost', port=8080)
print(config2.config)  # {'host': 'localhost', 'port': 8080}
# config2看到了config1的设置——因为它们是同一个对象

7.3 循环链表检测中的is

# 检测链表中是否有环——is的经典算法应用

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None


def has_cycle(head):
    """
    用快慢指针检测链表环。
    如果存在环,快慢指针最终会指向同一个节点(is判断)。
    """
    if head is None:
        return False
    
    slow = head
    fast = head
    
    while fast is not None and fast.next is not None:
        slow = slow.next
        fast = fast.next.next
        
        if slow is fast:  # 关键!用is判断是否指向同一个节点
            return True
    
    return False


# 创建带环的链表
n1 = Node(1)
n2 = Node(2)
n3 = Node(3)
n4 = Node(4)

n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n2  # 环!n4指向n2

print(f'有环: {has_cycle(n1)}')  # True

# 为什么这里必须用is而不是==?
# 因为我们关心的是"是否指向同一个节点对象"
# 即使两个节点的value相同,它们也可能是不同节点

7.4 缓存装饰器中的is

# 用is判断缓存命中

class CacheDecorator:
    def __init__(self):
        self._cache = {}
        self._NOT_CACHED = object()  # 哨兵
    
    def cached_call(self, func, *args):
        """带缓存——用sentinel区分'结果为None'和'未缓存'"""
        key = (func.__name__, args)
        result = self._cache.get(key, self._NOT_CACHED)
        
        if result is self._NOT_CACHED:  # is哨兵
            result = func(*args)
            self._cache[key] = result
            print(f'  [计算] {key} → {result}')
        else:
            print(f'  [缓存命中] {key} → {result}')
        
        return result


def expensive_computation(x, y):
    """模拟耗时计算"""
    import time
    time.sleep(0.5)  # 模拟
    return x * y + x + y

cache = CacheDecorator()

print('第一次调用:')
cache.cached_call(expensive_computation, 10, 20)

print('\n第二次调用(相同参数):')
cache.cached_call(expensive_computation, 10, 20)

print('\n第三次调用(不同参数):')
cache.cached_call(expensive_computation, 5, 8)

八、常见陷阱和注意事项

8.1 陷阱一:用is比较整数值

# ❌ 最常见的错误——依赖小整数缓存
def check_status(code):
    if code is 200:   # 危险!
        return 'OK'
    elif code is 404: # 危险!
        return 'Not Found'
    return 'Unknown'

# 在交互式环境或某些情况下,200和404在小整数范围(-5~256)内,is可能为True
# 但这是不可靠的实现细节!

# ✅ 正确方式
def check_status(code):
    if code == 200:
        return 'OK'
    elif code == 404:
        return 'Not Found'
    return 'Unknown'

8.2 陷阱二:用is比较字符串

# ❌ 错误——依赖字符串驻留
def authenticate(username):
    if username is 'admin':   # 危险!
        return True
    return False

# 从文件/网络/数据库读取的字符串不会自动驻留
# username = 'admin'   # 在代码中直接写的字符串可能被驻留,is可能为True
# username = 'adm' + 'in'  # 动态拼接的可能不驻留,is可能为False

# ✅ 正确方式
def authenticate(username):
    if username == 'admin':
        return True
    return False

8.3 陷阱三:is not 和 not … is 有细微差别

x = None

# 这两种写法在功能上等价
print(x is not None)    # True——推荐的写法(PEP 8)
print(not (x is None))  # True——等价,但可读性差

# ⚠️ 千万不要写成:
# print(x is (not None))  # 这完全没有意义!not None是True

8.4 陷阱四:nan的比较

import math

# nan(Not a Number)的特殊性
nan = float('nan')

# nan == nan 是 False!(IEEE 754规范)
print(nan == nan)  # False——nan不等于任何值,包括自己

# nan is nan 是 True(同一个对象)
print(nan is nan)  # True

# 正确检查nan的方式
print(math.isnan(nan))  # True

# ⚠️ 所以如果你想检查一个值是不是nan:
# ❌ if x == float('nan'):  ——永远False
# ✅ if math.isnan(x):

8.5 陷阱五:可变对象的is和==时序

# ==的结果可能随时间变化(对可变对象)
# is的结果不会变(对象身份不变)

lst1 = [1, 2, 3]
lst2 = [1, 2, 3]
lst3 = lst1

print(f'初始: lst1 == lst2: {lst1 == lst2}')  # True
print(f'初始: lst1 is lst2: {lst1 is lst2}')  # False
print(f'初始: lst1 is lst3: {lst1 is lst3}')  # True

# lst1被修改了
lst1.append(4)

# ==的结果变了(值变了)
print(f'修改后: lst1 == lst2: {lst1 == lst2}')  # False——值不相等了

# is的结果没变(身份没变)
print(f'修改后: lst1 is lst2: {lst1 is lst2}')  # False——还是不同对象
print(f'修改后: lst1 is lst3: {lst1 is lst3}')  # True——还是同一个对象

九、速查表

场景推荐说明
和None比较x is NonePEP 8推荐,最快最安全
比较数值==不要依赖整数缓存
比较字符串==不要依赖字符串驻留
比较列表/字典/集合==比的是内容
单例模式验证is判断是否是同一个实例
哨兵对象检测is区分"值为None"和"未设置"
类型检查isinstance()type() is更好
布尔值检查直接用if x:if x is True:

十、本篇小结

==is的区别是Python基础中的"高频面试考点+日常开发刚需":

==(相等性比较):

  • 比较两个对象的是否相等
  • 调用__eq__方法,可以被自定义类重载
  • 对可变对象,结果可能随时间变化
  • 适用场景:比较数值、字符串、列表内容、字典内容

is(身份比较):

  • 比较两个对象是否同一个对象(id是否相同)
  • 不能被重载,永远比较对象身份
  • 对同一个对象,结果永远不变
  • 适用场景:和None比较、哨兵对象、单例验证

关键记忆点:

  • a is b 等价于 id(a) == id(b)
  • 小整数缓存 [-5, 256] 和字符串驻留是"实现细节",不要在生产代码中依赖
  • 和None比较永远用is而不是==
  • 比较内容用==,比较身份用is

搞懂了is==,下一篇我们来学一个紧密相关的主题——None对象与空值判断的正确姿势。None是Python中最特殊的单例对象之一,正确理解和处理它,能让你的代码少很多Bug。

到此这篇关于Python基础指南之is与==的区别及使用场景详解的文章就介绍到这了,更多相关Python  is与==内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 对python 各种删除文件失败的处理方式分享

    对python 各种删除文件失败的处理方式分享

    下面小编就为大家分享一篇对python 各种删除文件失败的处理方式。具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-04-04
  • Python魔术方法深入分析讲解

    Python魔术方法深入分析讲解

    所谓魔法函数(Magic Methods),是Python的⼀种⾼级语法,允许你在类中⾃定义函数(函数名格式⼀般为__xx__),并绑定到类的特殊⽅法中。⽐如在类A中⾃定义__str__()函数,则在调⽤str(A())时,会⾃动调⽤__str__()函数,并返回相应的结果
    2023-02-02
  • python绘图demo实现流程介绍

    python绘图demo实现流程介绍

    这篇文章主要介绍了python绘图demo实现流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-11-11
  • PyQt5实现简单的计算器

    PyQt5实现简单的计算器

    这篇文章主要为大家详细介绍了PyQt5实现简单的计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • Pandas常用累计、同比、环比等统计方法实践过程

    Pandas常用累计、同比、环比等统计方法实践过程

    这篇文章主要介绍了Pandas常用累计、同比、环比等统计方法实践过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • python subprocess 杀掉全部派生的子进程方法

    python subprocess 杀掉全部派生的子进程方法

    下面小编就为大家带来一篇python subprocess 杀掉全部派生的子进程方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 基于Python实现网页文章转PDF文档

    基于Python实现网页文章转PDF文档

    有时候看到一篇好的文章,想去保存下来,传统方式一般是收藏书签、复制粘贴到文档或者直接复制链接保存,但这也太麻烦了。本文将用Python语言实现将网上的文章转存为PDF文档,保存电脑上慢慢看
    2022-05-05
  • Python实现的计算马氏距离算法示例

    Python实现的计算马氏距离算法示例

    这篇文章主要介绍了Python实现的计算马氏距离算法,简单说明了马氏距离算法原理,并结合实例形式分析了Python实现与使用马氏距离算法的相关操作技巧,需要的朋友可以参考下
    2018-04-04
  • Python自动化解压多种格式的压缩文件(支持.zip/.rar/.7z格式)

    Python自动化解压多种格式的压缩文件(支持.zip/.rar/.7z格式)

    这篇文章主要为大家详细介绍了如何使用Python实现自动化解压多种格式的压缩文件,包括.zip,.rar和.7z格式,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-06-06
  • Python使用reportlab将目录下所有的文本文件打印成pdf的方法

    Python使用reportlab将目录下所有的文本文件打印成pdf的方法

    这篇文章主要介绍了Python使用reportlab将目录下所有的文本文件打印成pdf的方法,涉及reportlab模块操作pdf文件的相关技巧,需要的朋友可以参考下
    2015-05-05

最新评论