飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

身份与成员运算符

身份运算符 is 和 is not 用于判断两个名字是否引用内存中的同一个对象;成员运算符 in 和 not in 用于判断某个值是否存在于容器或可迭代对象中。这两类运算符解决的问题与 == 完全不同,理解它们的差异是避免隐蔽 bug 的关键。

is 与 is not

is 比较的是对象的身份(identity),即它们在内存中的地址。两个对象即使值完全相同,如果存储在不同的内存位置,is 也返回 False。is not 是 is 的否定形式。

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True        # 值相等
>>> a is b
False       # 不是同一个对象
>>> id(a) == id(b)
False       # id() 返回对象身份,is 本质上比较 id

is 的核心使用场景是判断 None。由于 None 在 Python 中是单例(全局只有一个 None 对象),is None 是判断某个值是否为 None 的标准且最可靠的方式。

>>> x = None
>>> x is None
True
>>> x is not None
False

# 函数参数默认值检查
def process(data=None):
    if data is None:
        data = []
    # ...

为什么不用 == 判断 None?因为 == 可以被自定义类重载,而 is 不能。某个对象可能重载了 __eq__ 使其对 None 返回 True,但 is None 永远不会被欺骗。

class Weird:
    def __eq__(self, other):
        return True        # 对任何比较都返回 True

>>> w = Weird()
>>> w == None
True        # 被欺骗了
>>> w is None
False       # 安全的判断

== 与 is 的区别

运算符比较内容适用场景
==值相等(调用 __eq__)数字、字符串内容、数据结构
is身份相同(内存地址)None 判断、单例对象、同一对象检测

初学者常犯的错误是用 is 比较字符串或数字:

>>> a = 1000
>>> b = 1000
>>> a == b
True
>>> a is b
False       # 大整数不是单例

>>> a = "hello world"
>>> b = "hello world"
>>> a == b
True
>>> a is b
False       # 运行时构造的字符串通常不是同一对象

小整数缓存

Python 为了优化性能,对 -5 到 256 范围内的小整数进行了缓存,这些数字在任何地方使用都是同一个对象。

>>> a = 256
>>> b = 256
>>> a is b
True        # 在缓存范围内

>>> a = 257
>>> b = 257
>>> a is b
False       # 超出缓存范围,创建了两个对象

>>> a = -5
>>> b = -5
>>> a is b
True

>>> a = -6
>>> b = -6
>>> a is b
False

字符串也有类似的驻留(intern)机制,但仅限于编译期确定的标识符风格字符串,不应依赖此行为:

>>> a = "python"
>>> b = "python"
>>> a is b
True        # 标识符风格字符串可能被驻留

>>> a = "hello world!"
>>> b = "hello world!"
>>> a is b
False       # 含空格和标点,通常不驻留

依赖 is 进行值比较是不可靠的编程习惯,应始终使用 ==。

in 与 not in

in 判断左侧操作数是否是右侧容器或可迭代对象的成员,返回 True 或 False。not in 是其否定形式。

>>> 3 in [1, 2, 3, 4]
True
>>> 5 in [1, 2, 3, 4]
False
>>> "a" in "banana"
True
>>> "z" not in "banana"
True

不同容器类型的 in 操作效率差异显著:

  • 列表、元组:线性扫描,时间复杂度 O(n)
  • 集合、字典(判断键):哈希查找,时间复杂度 O(1)
  • 字符串:子串匹配,使用高效的 C 级算法
>>> 3 in {1, 2, 3, 4}       # 集合,O(1)
True
>>> "name" in {"name": "Alice", "age": 25}   # 字典判断键,O(1)
True
>>> "Alice" in {"name": "Alice", "age": 25}  # 不判断值!
False

字典的 in 只检查键,不检查值。如果需要判断值是否存在,应使用 .values():

>>> "Alice" in {"name": "Alice", "age": 25}.values()
True

可迭代对象的成员检查

in 运算符依赖对象的 __contains__ 方法。如果对象没有实现该方法,Python 会尝试迭代对象来查找匹配项。

>>> 2 in range(5)
True        # range 实现了高效的 __contains__

>>> (1, 2) in [(1, 2), (3, 4)]
True        # 元组作为列表元素

>>> 2 in (x for x in range(10) if x % 2 == 0)
True        # 生成器表达式,逐元素检查

自定义类可以通过实现 __contains__ 来支持 in 运算:

class Range:
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __contains__(self, value):
        return self.start <= value < self.end

>>> r = Range(10, 20)
>>> 15 in r
True
>>> 20 in r
False       # 右开区间

嵌套容器的成员检查

in 对嵌套容器只检查顶层成员,不进行递归搜索:

>>> 3 in [1, [2, 3], 4]
False       # 3 不在顶层,[2, 3] 才是顶层元素
>>> [2, 3] in [1, [2, 3], 4]
True

如果需要递归查找,应自行实现或使用专门的库函数。

身份运算符的边界情况

对于布尔值,True 和 False 是 1 和 0 的单例,因此:

>>> True is 1
False       # True 是 bool 类型,1 是 int 类型,身份不同
>>> True == 1
True        # 但值相等
>>> False is 0
False

在 Python 3 中,bool 是 int 的子类,但 True 和 1 仍是不同对象。

身份与成员运算符解决的问题领域与 == 截然不同。is 回答"是否是同一个东西",in 回答"是否在其中",而 == 回答"是否相等"。用 is 判断 None、用 == 判断值、用 in 判断成员关系,是 Python 编程中应当遵循的基本约定。

上一页
位运算符
下一页
海象运算符