运算符:算术、比较、逻辑、赋值、位运算
一句话概述
运算符是 Python 中执行计算和逻辑判断的"动作动词"——没有它们,数据就是一堆不会动的积木。Python 提供了五大类运算符:算术运算符(加减乘除)做数学计算,比较运算符(大于小于)比大小产出布尔值,逻辑运算符(and/or/not)组合多个判断条件,赋值运算符(=, +=, -=)把计算结果存回变量,位运算符(&, |, ^)直接操作二进制位。运算符看起来简单,但其中的优先级规则、短路逻辑、链式比较和整数除法的细节,是初学者最容易踩坑的地带。本节会用大量可运行代码带你逐个击破。
💡 核心要点:①算术运算符中/总是返回 float,//是整除向下取整 ②比较运算符支持链式写法a < b < c③逻辑运算符 and/or 支持短路求值,返回的是操作数本身而非布尔值 ④位运算直接操作二进制位,在底层优化和图像处理中必不可少 ⑤运算符优先级:算术 > 比较 > 逻辑,记不清就加括号
教学与演示
一、算术运算符——加减乘除的基础
是什么:算术运算符就是数学中的四则运算:+(加)、-(减)、*(乘)、/(除)、//(整除)、%(取余/模)、**(幂)。其中 / 是 Python 3 中最重要的变化——即使是两个整数相除,结果也总是浮点数。// 是整除(地板除),结果向下取整。% 取余的结果符号与除数相同。
大白话 小学学的加减乘除在这里基本一样,但有两个"坑"要记住:第一,5 / 2等于2.5而不是2(这是 Python 3 故意这么设计的,避免意外丢失精度);第二,5 // 2等于2、但-5 // 2等于-3(向下取整,不是向零取整!)。取余%平时用来判断奇偶(n % 2),AI 中用来循环索引(index % len(data))。
为什么:算术运算是数值计算的基础,AI 中的一切数学操作——从前向传播的加权求和($Wx + b$),到损失函数的均方误差($\sum(y - \hat{y})^2$),再到梯度下降的参数更新($w = w - \eta \cdot \nabla w$),本质上都是算术运算的组合。
大白话 你看到的那些"高大上"的 AI 模型,拆开看底层就是一堆加减乘除和指数运算。神经网络的一层说白了就是 y = W @ x + b——矩阵乘法 @ 是乘加组合,加法 + 是偏置。所以别小看这些基础运算符,AI 大厦就是由它们一砖一瓦垒起来的。
怎么做:
import numpy as np
# ====== 1. 基本算术运算 ======
a, b = 17, 5
print("=== 基本算术运算 ===")
print(f"a = {a}, b = {b}")
print(f"{a} + {b} = {a + b}") # 17 + 5 = 22 — 加法
print(f"{a} - {b} = {a - b}") # 17 - 5 = 12 — 减法
print(f"{a} * {b} = {a * b}") # 17 * 5 = 85 — 乘法
print(f"{a} / {b} = {a / b}") # 17 / 5 = 3.4 — 真除法,结果总是 float
print(f"{a} // {b} = {a // b}") # 17 // 5 = 3 — 整除(向下取整)
print(f"{a} % {b} = {a % b}") # 17 % 5 = 2 — 取余(17 = 5×3 + 2)
print(f"{a} ** {b} = {a ** b}") # 17 ** 5 = 1419857 — 幂运算
# ====== 2. 整除的"向下取整"陷阱 ======
print("\n=== 整除的向下取整 ===")
# 正数整除:符合直觉
print(f"7 // 3 = {7 // 3}") # 2 — 2.333... 向下取整到 2
print(f"7 // 2 = {7 // 2}") # 3 — 3.5 向下取整到 3
# 负数整除:可能出乎意料!
print(f"-7 // 3 = {-7 // 3}") # -3 — 不是 -2!
# 解释:-7/3 = -2.333...,向下取整(更小的整数)是 -3
print(f"-7 // 2 = {-7 // 2}") # -4 — 不是 -3!
# 解释:-7/2 = -3.5,向下取整是 -4
# 如果你想要"向零取整",用 int()
print(f"int(-7 / 3) = {int(-7 / 3)}") # -2 — 直接砍掉小数(向零取整)
# ====== 3. 取余运算的性质 ======
print("\n=== 取余运算 ===")
# Python 取余的结果符号与除数相同
print(f"17 % 5 = {17 % 5}") # 2 — 正数取余,正常
print(f"-17 % 5 = {-17 % 5}") # 3 — 不是 -2!(-17 = 5×(-4) + 3)
print(f"17 % -5 = {17 % -5}") # -3 — 符号跟除数(-5)
print(f"-17 % -5 = {-17 % -5}") # -2 — 符号跟除数(-5)
# 应用:判断奇偶
for n in [2, 5, 8, 11]:
parity = "偶数" if n % 2 == 0 else "奇数"
print(f" {n} 是{parity}")
# 应用:循环索引(AI 中常见)
data_len = 5
for i in range(10):
circular_idx = i % data_len # 保证索引在 0~4 之间循环
print(f" 原始索引 {i} → 循环索引 {circular_idx}")
# ====== 4. 幂运算与内置函数 ======
print("\n=== 幂运算 ===")
# ** 和 pow() 等价
print(f"2 ** 10 = {2 ** 10}") # 1024
print(f"pow(2, 10) = {pow(2, 10)}") # 1024
# pow 还可以接受第三个参数:pow(a, b, mod) = a**b % mod
print(f"pow(2, 10, 1000) = {pow(2, 10, 1000)}") # 24 — 2^10 % 1000
# ====== 5. 运算符优先级演示 ======
print("\n=== 运算符优先级 ===")
# 优先级从高到低:** > 正负号 > * / // % > + -
print(f"2 + 3 * 4 = {2 + 3 * 4}") # 14 — 先乘后加
print(f"(2 + 3) * 4 = {(2 + 3) * 4}") # 20 — 加括号改变顺序
print(f"2 ** 3 * 2 = {2 ** 3 * 2}") # 16 — 先幂(8)再乘2
print(f"2 ** (3 * 2) = {2 ** (3 * 2)}") # 64 — 先乘(6)再幂
# 记不住优先级?加括号就对了!
# ====== 6. NumPy 中的向量化算术 ======
print("\n=== NumPy 向量化算术 ===")
arr = np.array([1, 2, 3, 4, 5])
# 每个元素都做同样的运算(向量化,比 Python 循环快得多)
print(f"原始数组: {arr}")
print(f"每个元素 +10: {arr + 10}") # [11 12 13 14 15]
print(f"每个元素 *2: {arr * 2}") # [2 4 6 8 10]
print(f"每个元素平方: {arr ** 2}") # [1 4 9 16 25]
print(f"每个元素 %3: {arr % 3}") # [1 2 0 1 2]
# NumPy 的广播运算让批量数据操作非常简洁什么用:算术运算符在 AI 中最常见的应用场景:①数据预处理——标准化数据 (x - mean) / std 用的就是减法和除法;②损失计算——均方误差 $\frac{1}{n}\sum(y - \hat{y})^2$ 是减法、幂、加法、除法的组合;③学习率衰减——lr = lr_init * (1 - epoch / total) 是乘除减的组合;④分批索引——batch_idx = i % num_batches 用取余实现循环取数据。
二、比较运算符——比大小出真值
是什么:比较运算符用于比较两个值的大小关系,结果总是布尔值(True 或 False)。包括:==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)。Python 还支持链式比较,如 1 < x < 10 判断 x 是否在 1 到 10 之间。
大白话 比较运算符就是"问问题"——代码在执行时会不停地问:"这个数比那个大吗?""这两个相等吗?"然后根据"是"或"否"决定下一步走哪条路。Python 的链式比较是个贴心设计——1 < x < 10 你读起来就是"x 大于 1 且小于 10",和人类的思维一样自然。
为什么:比较运算是条件判断的基础。没有比较,就没有 if/elif/else,就没有 while 循环的退出条件,就没有数据过滤和筛选。AI 中大量使用比较运算来判断模型表现——if accuracy > best_accuracy: save_model()、while loss > threshold: continue_training()。
大白话 比较运算符是程序的"决策引擎"。每个 if 语句背后,都有一个==、>或<在默默工作。没有它们,你的程序就像一辆没有方向盘的汽车——只能直行,无法转向。
怎么做:
import numpy as np
# ====== 1. 六种比较运算符 ======
x, y = 10, 3
print("=== 比较运算符 ===")
print(f"x = {x}, y = {y}")
print(f"{x} == {y}: {x == y}") # False — 等于
print(f"{x} != {y}: {x != y}") # True — 不等于
print(f"{x} > {y}: {x > y}") # True — 大于
print(f"{x} < {y}: {x < y}") # False — 小于
print(f"{x} >= {y}: {x >= y}") # True — 大于等于
print(f"{x} <= {y}: {x <= y}") # False — 小于等于
# ====== 2. 不同类型之间的比较 ======
print("\n=== 跨类型比较 ===")
# 数字类型之间可以比较
print(f"3 == 3.0: {3 == 3.0}") # True — int 和 float 比较自动转换
print(f"5 > 3.5: {5 > 3.5}") # True
# 不同数字类型之间也可以
print(f"True == 1: {True == 1}") # True — bool 是 int 的子类
print(f"False == 0: {False == 0}") # True
# 字符串之间的比较(按 Unicode 码点顺序,类似字典序)
print(f"'a' < 'b': {'a' < 'b'}") # True
print(f"'apple' < 'banana': {'apple' < 'banana'}") # True
print(f"'Apple' < 'apple': {'Apple' < 'apple'}") # True — 大写字母码点更小
print(f"'2' < '10': {'2' < '10'}") # False — 字符串逐字符比较,'2' > '1'
# 不同类型的对象通常不能比较(会报 TypeError)
# print(3 < "hello") # ❌ TypeError
# ====== 3. 链式比较(Python 特色!) ======
print("\n=== 链式比较 ===")
age = 25
# 传统写法:age >= 18 and age <= 60
# Python 链式写法:更简洁直观
print(f"18 <= {age} <= 60: {18 <= age <= 60}") # True — 等效于 age>=18 and age<=60
score = 85
print(f"0 <= {score} <= 100: {0 <= score <= 100}") # True — 判断分数是否有效
# 链式比较本质:a < b < c 等价于 a < b and b < c
# 但 b 只被求值一次,更高效
a, b, c = 1, 5, 10
print(f"{a} < {b} < {c}: {a < b < c}") # True
# ====== 4. is 和 is not(身份比较) ======
print("\n=== is 与 is not ===")
# is 判断是否同一个对象(内存地址相同),不是值相同
a = [1, 2, 3]
b = [1, 2, 3] # 值相同但不同的列表对象
c = a # c 和 a 指向同一个对象
print(f"a == b: {a == b}") # True — 值相等
print(f"a is b: {a is b}") # False — 不同对象
print(f"a is c: {a is c}") # True — 同一对象
print(f"a is not b: {a is not b}") # True — 不是同一对象
# None 判断必须用 is(None 是单例)
x = None
print(f"x is None: {x is None}") # True — 正确做法
print(f"x == None: {x == None}") # True — 也能工作,但不推荐
# ====== 5. in 和 not in(成员测试) ======
print("\n=== in 与 not in ===")
fruits = ["苹果", "香蕉", "橘子"]
print(f"'苹果' in {fruits}: {'苹果' in fruits}") # True
print(f"'西瓜' in {fruits}: {'西瓜' in fruits}") # False
print(f"'西瓜' not in {fruits}: {'西瓜' not in fruits}") # True
# in 也适用于字符串(子串判断)
text = "Hello, World!"
print(f"'World' in '{text}': {'World' in text}") # True
print(f"'Python' in '{text}': {'Python' in text}") # False
# ====== 6. NumPy 的比较运算(返回布尔数组) ======
print("\n=== NumPy 数组比较 ===")
scores = np.array([85, 92, 78, 95, 60, 88])
print(f"成绩数组: {scores}")
# 对每个元素做比较,返回布尔数组
passing = scores >= 60 # 及格判断
print(f"scores >= 60: {passing}") # [True True True True True True]
excellent = scores >= 90 # 优秀判断
print(f"scores >= 90: {excellent}") # [False True False True False False]
# 用布尔数组做筛选(AI 数据过滤的超常用操作)
high_scores = scores[scores >= 85] # 只保留 >=85 的成绩
print(f"高分成绩(>=85): {high_scores}") # [85 92 95 88]
# 统计满足条件的数量
print(f"优秀人数(>=90): {np.sum(excellent)}") # 2 — sum 对 True 计数什么用:在 AI 中,比较运算无处不在。数据清洗阶段用 df[df['age'] > 0] 过滤无效数据;模型评估阶段用 predicted == true_labels 计算准确率;训练循环中用 if loss < best_loss: 保存最佳模型;早停机制中用 if patience >= threshold: break 终止训练。NumPy 的布尔数组索引更是数据筛选的核心武器。
三、逻辑运算符——组合判断条件
是什么:Python 的逻辑运算符有三个:and(与)、or(或)、not(非)。它们用于组合多个布尔条件。Python 的逻辑运算符和大多数语言不同——它们返回的是操作数本身的值,而不一定是 True/False。这个特性源于 Python 的"短路求值"机制,可以用 and/or 写出非常简洁的代码。
大白话and就像"而且"——两个条件都得满足,缺一不可。or就像"或者"——只要满足一个就行。not就是"取反"——把是对变成错、把错变成对。Python 的特色是:a and b如果 a 是假的就直接返回 a(不管 b),否则返回 b;a or b如果 a 是真的就直接返回 a(不管 b),否则返回 b。这个"短路"特性在一些场景下超好用。
为什么:实际判断很少只有一个条件——"如果年龄大于 18 并且不是学生"、"如果成绩大于 90 或者有竞赛加分"。逻辑运算符让你能把多个简单条件组合成复杂条件。短路求值不仅提高了效率(避免不必要的计算),还让你能写出 name and name.strip() 这样的安全代码——如果 name 是 None 就不会执行后面的 name.strip()。
大白话 就像点餐——"我要一杯咖啡,如果没有就给我白开水"。or运算符能一行代码表达这个逻辑:drink = coffee or water。
怎么做:
import numpy as np
# ====== 1. 逻辑运算符的真值表 ======
print("=== 逻辑运算符 ===")
print("=== AND 真值表 ===")
print(f"True and True = {True and True}") # True
print(f"True and False = {True and False}") # False
print(f"False and True = {False and True}") # False
print(f"False and False = {False and False}") # False
print("\n=== OR 真值表 ===")
print(f"True or True = {True or True}") # True
print(f"True or False = {True or False}") # True
print(f"False or True = {False or True}") # True
print(f"False or False = {False or False}") # False
print(f"\nnot True = {not True}") # False
print(f"not False = {not False}") # True
# ====== 2. 短路求值的应用(返回操作数本身) ======
print("\n=== 短路求值:and 返回第一个 Falsy 值或最后一个值 ===")
print(f"'hello' and 'world': {'hello' and 'world'}") # 'world' — 第一个是真,看第二个
print(f"'' and 'world': {'' and 'world'}") # '' — 第一个是假,直接返回
print(f"0 and 100: {0 and 100}") # 0 — 0 是 Falsy
print(f"[] and [1,2]: {[] and [1,2]}") # [] — 空列表是 Falsy
print("\n=== 短路求值:or 返回第一个 Truthy 值或最后一个值 ===")
print(f"'hello' or 'world': {'hello' or 'world'}") # 'hello' — 第一个是真,直接返回
print(f"'' or 'world': {'' or 'world'}") # 'world' — 第一个是假,看第二个
print(f"0 or 100: {0 or 100}") # 100 — 0 是 Falsy
print(f"None or '默认值': {None or '默认值'}") # '默认值' — 超常用!
# ====== 3. 短路求值的实用场景 ======
# 场景1:设置默认值
username = None # 可能为 None 的用户名
display_name = username or "游客" # 如果 username 为空就显示"游客"
print(f"\n显示名称: {display_name}") # 游客
# 场景2:安全访问(避免 None 的属性访问报错)
data = None
# 如果 data 是 None,短路直接返回 None,不会执行 data.get()
result = data and data.get('key', 'default')
print(f"安全访问结果: {result}") # None(不会报错!)
# 场景3:条件执行
is_debug = True
# 如果 is_debug 为 True,才执行后面的 print
is_debug and print("调试模式已开启") # 会打印
is_debug = False
is_debug and print("这条不会打印") # 不会打印
# ====== 4. 逻辑运算符 vs 位运算符 ======
# | 和 & 是位运算符,用在布尔值上不会短路!
# and 和 or 是逻辑运算符,会短路
# 这是初学者经常搞混的!
print("\n=== 逻辑 vs 位运算符 ===")
# and: 短路逻辑运算符(推荐用于布尔逻辑)
print(f"True and False: {True and False}") # False
# & : 位运算符(不短路,两个操作数都会被求值)
print(f"True & False: {True & False}") # False(结果一样但语义不同)
# 对于布尔值,and 和 & 结果相同;但 and 会短路,& 不会
# 在 NumPy 数组中,必须用 & | 来做逐元素逻辑操作
a = np.array([True, False, True])
b = np.array([False, False, True])
print(f"NumPy 逐元素与 a & b: {a & b}") # [False False True]
print(f"NumPy 逐元素或 a | b: {a | b}") # [True False True]
print(f"NumPy 逐元素非 ~a: {~a}") # [False True False]
# 注意:NumPy 中不能用 and/or/not,因为它们只适用于单个布尔值
# ====== 5. 逻辑运算的优先级 ======
print("\n=== 逻辑运算优先级 ===")
# not > and > or
print(f"not True or True: {not True or True}") # True(先 not 后 or)
print(f"not (True or True): {not (True or True)}") # False
print(f"True or True and False: {True or True and False}") # True(先 and 后 or)
print(f"(True or True) and False: {(True or True) and False}") # False
# 最佳实践:用括号明确优先级,不要依赖默认优先级!什么用:逻辑运算在 AI 中极其重要。数据清洗时用复杂条件过滤数据(df[(df['age'] > 18) & (df['gender'] == 'M')]——注意 Pandas 中用 & 而非 and);模型评估中的多条件判断(if precision > 0.8 and recall > 0.7);早停逻辑(if consecutive_no_improve > patience or loss < min_loss)。NumPy/Pandas 的逐元素逻辑运算(&, |, ~)与 Python 的逻辑运算符(and, or, not)一定要区分清楚,混用会导致难以调试的错误。
四、赋值运算符——存结果到变量
是什么:Python 中,= 是最基本的赋值运算符。除此之外还有复合赋值运算符:+=、-=、*=、/=、//=、%=、**=,它们把运算和赋值合二为一。Python 3.8+ 还引入了海象运算符 :=,允许在表达式中同时赋值和使用。
大白话x += 5就是x = x + 5的简写。就像记账——"现有余额再加一笔",不用每次都写两遍余额。复合赋值不仅是更短,而且在某些场景下更高效(比如累加到一个大列表时)。海象运算符:=就像"边量尺寸边裁衣服"——在判断条件的同时把值存下来,省得后面再算一遍。
为什么:复合赋值让代码更简洁,减少重复键入和潜在错误(变量名只需写一次)。在循环累加、计数器更新等场景中尤其常用。海象运算符 := 减少了重复计算,让 if 和 while 语句更紧凑。
大白话count = count + 1写 100 遍不仅手累,还容易把 count 拼错。count += 1就清晰多了。AI 训练中的loss += batch_loss、step += 1都是标配写法。
怎么做:
import numpy as np
# ====== 1. 基本赋值 ======
print("=== 基本赋值 ===")
x = 10 # 把 10 赋值给 x
print(f"x = {x}")
# 多重赋值(同时给多个变量赋值)
a, b, c = 1, 2, 3 # a=1, b=2, c=3
print(f"a={a}, b={b}, c={c}")
# 交换变量值(Python 特色的优雅写法)
a, b = b, a # 交换 a 和 b 的值
print(f"交换后: a={a}, b={b}") # a=2, b=1
# ====== 2. 复合赋值运算符 ======
print("\n=== 复合赋值运算符 ===")
n = 10
print(f"初始 n = {n}")
n += 5 # n = n + 5
print(f"n += 5 → {n}") # 15
n -= 3 # n = n - 3
print(f"n -= 3 → {n}") # 12
n *= 2 # n = n * 2
print(f"n *= 2 → {n}") # 24
n /= 4 # n = n / 4
print(f"n /= 4 → {n}") # 6.0(注意:结果是 float!)
n //= 2 # n = n // 2
print(f"n //= 2 → {n}") # 3.0
n **= 3 # n = n ** 3
print(f"n **= 3 → {n}") # 27.0
n %= 7 # n = n % 7
print(f"n %= 7 → {n}") # 6.0
# ====== 3. 海象运算符 :=(Python 3.8+) ======
print("\n=== 海象运算符 := ===")
# 场景1:在 while 循环中同时读取和判断
# 传统写法(需要两行):
# line = input()
# while line != "quit":
# process(line)
# line = input()
# 海象运算符写法(一行搞定):
data = [85, 92, 78, 95, 60, 88] # 模拟数据
i = 0
# 在条件判断中赋值并使用:先赋值给 score,再判断 score >= 80
while (score := data[i] if i < len(data) else -1) >= 80:
print(f" 取出高分: {score}")
i += 1
# 场景2:在 if 中避免重复计算
text = " hello world "
if (stripped := text.strip()) != "": # 先 strip 赋值给 stripped,再判断
print(f"\n处理结果: {stripped.upper()}") # 直接用 stripped,不用再调 strip
# 传统写法:if text.strip() != "": result = text.strip() (调了两次 strip)
# 场景3:在列表推导中使用
# 传统:lambda 或嵌套推导
# 海象:直接在推导式中计算并复用
numbers = [3, 7, 12, 5, 9, 15]
# 保留平方值大于 50 的那些数
filtered = [(n, sq) for n in numbers if (sq := n ** 2) > 50]
print(f"\n平方>50的数: {filtered}") # [(7,49)? 不对,7²=49<50]
# 输出: [(8,...)]—不,让我们看实际结果
# 实际:8²=64, 12²=144, 9²=81, 15²=225
print(f"结果: {filtered}")
# ====== 4. 累加模式回顾(AI 训练中的超常见模式) ======
print("\n=== AI 训练中的累加模式 ===")
# 模拟训练循环中的损失累加
total_loss = 0.0 # 初始化总损失
batch_losses = [2.3, 1.8, 2.1, 1.5, 2.0] # 模拟每个批次的损失
num_batches = len(batch_losses)
for i, loss in enumerate(batch_losses):
total_loss += loss # 等价于 total_loss = total_loss + loss
# 这是训练循环中最常见的代码模式
avg_loss = total_loss / num_batches # 计算平均损失
print(f"总损失: {total_loss:.1f}") # 9.7
print(f"平均损失: {avg_loss:.2f}") # 1.94
# ====== 5. 赋值链 ======
# Python 支持链式赋值
x = y = z = 0 # x, y, z 都指向同一个 0 对象
print(f"\n链式赋值: x={x}, y={y}, z={z}")
# 注意:对于可变对象,链式赋值要小心!
a = b = [] # a 和 b 指向同一个空列表!
a.append(1) # 修改 a 也会影响 b!
print(f"a = {a}, b = {b}") # a = [1], b = [1] — b 也变了!什么用:复合赋值在 AI 训练循环中无处不在。loss += batch_loss 累加每批损失,step += 1 更新训练步数,lr *= 0.99 做学习率衰减,gradient /= batch_size 求平均梯度。海象运算符 := 在数据处理和模型评估中特别有用——比如在 while 中边读数据边判断是否继续训练,避免了重复计算和中间变量。
五、位运算符——直接操作二进制
是什么:位运算符直接对整数的二进制表示进行逐位操作。包括:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。它们操作的是二进制位(bit)——计算机最底层的 0 和 1。
大白话 位运算就像"直接和开关打交道"——把数字想象成一排电灯开关(0=关,1=开),&就是"两个开关都开才开",|就是"任意一个开就开",^就是"两个开关状态不同才开",~就是"把所有开关反过来"。<<和>>是整体"移位"——左移一位相当于乘以 2,右移一位相当于整除 2(比*2和//2快一点点)。
为什么:位运算在普通应用中不常见,但在底层优化、图像处理、加密算法和嵌入式系统中广泛使用。在 AI 中,位运算用于高效的数据压缩、量化(模型压缩的一种技术)、哈希函数和特征工程。理解位运算能让你写出更高效的底层代码。
大白话 你可能不会每天用位运算写 AI 代码,但理解它有几个好处:①看懂开源代码中的位操作(比如图像处理中的颜色分离);②理解 PyTorch/TensorFlow 的量化(INT8)压缩原理;③处理二进制文件、协议解析、硬件编程等场景时能直接上手。
怎么做:
import numpy as np
# ====== 1. 位运算基础:看二进制 ======
print("=== 位运算基础 ===")
a, b = 5, 3
# 5 的二进制: 0101
# 3 的二进制: 0011
print(f"a = {a}, 二进制: {bin(a)}") # 0b0101
print(f"b = {b}, 二进制: {bin(b)}") # 0b0011
# ====== 2. 按位与 & ======
# 两个位都是1结果才是1
# 0101 (5)
# &0011 (3)
# ──────
# 0001 (1)
print(f"\n按位与: {a} & {b} = {a & b}") # 1
print(f" {bin(a)}\n& {bin(b)}\n= {bin(a & b)}")
# 应用:判断奇偶(num & 1 比 num % 2 快一点)
for n in [4, 7, 10, 13]:
parity = "偶数" if (n & 1) == 0 else "奇数"
print(f" {n} & 1 = {n & 1} → {parity}")
# 应用:清零特定位(掩码操作)
num = 0b1111 # 15,二进制 1111
mask = 0b1010 # 10,二进制 1010
# 只保留 mask 中为 1 的位
print(f"\n掩码: {bin(num)} & {bin(mask)} = {bin(num & mask)}") # 1010
# ====== 3. 按位或 | ======
# 任意一个位为1结果就是1
# 0101 (5)
# |0011 (3)
# ──────
# 0111 (7)
print(f"\n按位或: {a} | {b} = {a | b}") # 7
print(f" {bin(a)}\n| {bin(b)}\n= {bin(a | b)}")
# 应用:设置特定位
num = 0b1000 # 8,二进制 1000
flag = 0b0011 # 3,二进制 0011
# 把 flag 中为 1 的位"打开"
print(f"设位: {bin(num)} | {bin(flag)} = {bin(num | flag)}") # 1011
# ====== 4. 按位异或 ^ ======
# 两个位不同结果才是1
# 0101 (5)
# ^0011 (3)
# ──────
# 0110 (6)
print(f"\n按位异或: {a} ^ {b} = {a ^ b}") # 6
print(f" {bin(a)}\n^ {bin(b)}\n= {bin(a ^ b)}")
# 特殊的异或性质
print(f"x ^ x = 0: {a} ^ {a} = {a ^ a}") # 0 — 自己异或自己得0
print(f"x ^ 0 = x: {a} ^ 0 = {a ^ 0}") # 5 — 异或0不变
# 应用:不借助临时变量交换两个数
x, y = 10, 20
print(f"\n交换前: x={x}, y={y}")
x = x ^ y # 第一步
y = x ^ y # 第二步
x = x ^ y # 第三步
print(f"交换后: x={x}, y={y}") # x=20, y=10
# ====== 5. 按位取反 ~ ======
# 把所有位取反(0变1,1变0)
# Python 使用补码:~x = -(x + 1)
print(f"\n按位取反: ~{a} = {~a}") # ~5 = -6
print(f"~5 = -(5+1) = -6")
print(f"~0 = {~0}") # -1
# ====== 6. 左移 << 和右移 >> ======
print(f"\n=== 移位运算 ===")
n = 5 # 二进制: 101
print(f"n = {n}, 二进制: {bin(n)}")
# 左移:所有位向左移动,右侧补0(等价于乘以2的幂)
print(f"n << 1 = {n << 1} ({bin(n << 1)})") # 10 (1010) — 等价于 n*2
print(f"n << 2 = {n << 2} ({bin(n << 2)})") # 20 (10100) — 等价于 n*4
print(f"n << 3 = {n << 3} ({bin(n << 3)})") # 40 (101000) — 等价于 n*8
# 右移:所有位向右移动,左侧补符号位(等价于整除2的幂)
print(f"n >> 1 = {n >> 1} ({bin(n >> 1)})") # 2 (10) — 等价于 n//2
print(f"n >> 2 = {n >> 2} ({bin(n >> 2)})") # 1 (1) — 等价于 n//4
# 负数右移(算术右移,左侧补1保持符号)
m = -16
print(f"\nm = {m}, 二进制: {bin(m)}")
print(f"m >> 1 = {m >> 1} ({bin(m >> 1)})") # -8
# ====== 7. AI 中的位运算应用场景 ======
print("\n=== AI 中的位运算应用 ===")
# 场景1:高效的特征编码(one-hot 可以用位表示)
# 假设有 4 个特征,用 4 个 bit 表示是否开启
features = 0b0000 # 初始:所有特征关闭
features |= (1 << 0) # 开启第0个特征 → 0001
features |= (1 << 2) # 开启第2个特征 → 0101
print(f"特征位掩码: {bin(features)}") # 0b0101
print(f"特征0是否开启: {bool(features & (1 << 0))}") # True
print(f"特征1是否开启: {bool(features & (1 << 1))}") # False
print(f"特征2是否开启: {bool(features & (1 << 2))}") # True
# 场景2:颜色通道的分离(图像处理中常见)
# RGB 颜色用 24bit 整数表示:R(8bit)G(8bit)B(8bit)
color = 0xFF8040 # 橙色的 RGB 值
r = (color >> 16) & 0xFF # 提取红色通道
g = (color >> 8) & 0xFF # 提取绿色通道
b = color & 0xFF # 提取蓝色通道
print(f"颜色 0x{color:06X} → R={r}, G={g}, B={b}")什么用:位运算在 AI 中的实际应用包括:①模型量化——将 float32 权重转换为 int8,位运算实现快速推理;②图像数据增强——用位移和掩码修改像素颜色通道;③哈希特征——用异或运算快速生成特征哈希,用于大规模特征工程;④硬件加速——GPU 的位运算单元比浮点单元更多更快,量化的神经网络利用这一点提速;⑤Bloom Filter——用位运算实现高效的内存中集合查询,用于去重和缓存。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 算术运算符 | 加减乘除等数学运算 | 损失计算、梯度更新、数据标准化 | NumPy向量化 |
| 整除 // | 向下取整的除法 | 批次索引、循环轮次计算 | 取余 % |
| 比较运算符 | 比大小返回布尔值 | 早停判断、数据过滤、模型评估 | 链式比较 |
| 逻辑运算符 | and/or/not组合条件 | 多条件数据筛选、训练控制 | 短路求值 |
| 赋值运算符 | =和+=等存结果 | 累加损失、更新计数器、衰减学习率 | 海象运算符 |
| 位运算符 | 操作二进制位 | 模型量化、颜色处理、特征哈希 | 掩码、移位 |
| 短路求值 | 只计算必要的操作数 | 安全默认值、防御性编程 | 真值测试 |
| 运算符优先级 | 运算的执行顺序 | 写对复杂表达式 | 括号 |
重点答疑
Q1: is 和 == 到底什么时候用哪个?
is 判断两个变量是否指向同一个内存对象(身份相等),== 判断两个变量的值是否相等。记忆口诀:is 是同一个东西,== 是一样的东西。实际指导:①判断是否为 None 永远用 is(x is None),因为 None 是单例;②判断是否为 True/False 直接用 if x: 而非 if x is True:;③非 None/True/False 的场景用 ==。is 速度微快但语义不同,大多数情况你要的其实是 ==。
Q2: Python 的 //(整除)为什么负数结果让人觉得奇怪?
因为 // 是"向下取整",不是"向零取整"。"向下"的意思是取更小的整数——-7/3 = -2.333...,更小的整数是 -3(因为 -3 < -2.333 < -2)。这个设计保持了 a == (a // b) * b + (a % b) 这个数学等式成立——如果改成向零取整(int(-7/3) = -2),那取余 -7 % 3 就要是 -1 才能保持等式,这与 Python 的 % 结果(符号跟随除数)不一致。大多数语言的整除都和 Python 一样是向下取整,理解即可。
Q3: and/or 和 &/| 有什么区别?什么时候报错?
本质区别:and/or 是逻辑运算符,会短路,返回操作数本身;&/| 是位运算符,不短路,对整数做逐位操作。布尔值场景下两者结果相同,但:①对于 NumPy 数组,必须用 &/|(因为 and/or 不能对数组做逐元素操作);②对于单个布尔值,推荐用 and/or(短路 = 更高效 + 更清晰);③混用时注意括号——& 优先级比 == 高,(a > 0) & (b < 10) 的括号不能省。
Q4: 海象运算符 := 有什么坑吗?
海象运算符最大的坑是优先级——它需要加括号括起来。比如 if x := get_value(): 会报错,必须写成 if (x := get_value()):。另一个坑是可读性——过度使用海象会让代码难以理解,特别是嵌套使用时。建议:只在 while 循环和避免重复计算的简单 if 场景中使用,不要在推导式中嵌套多层海象。
Q5: 位运算在 AI 中真的常用吗?
对于大多数应用层 AI 开发者,位运算不常直接使用。但在以下场景很关键:①模型量化工程师——将 float 权重转 int8 时需要位运算来重组字节;②嵌入式 AI/NN API——移动端和 IoT 设备的 AI 框架大量使用位运算;③高性能计算——了解位运算有助于理解 GPU 内部的 warp shuffle 等高级优化;④阅读底层框架源码——PyTorch 和 TensorFlow 的底层 C++/CUDA 代码大量使用位运算。你可以不会写,但要能读懂。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| operator | /ˈɒpəreɪtər/ | 运算符,执行运算的符号 |
| arithmetic | /əˈrɪθmətɪk/ | 算术的,加减乘除等 |
| floor division | /flɔːr dɪˈvɪʒən/ | 整除,向下取整的除法 |
| modulo | /ˈmɒdjʊloʊ/ | 取余/模运算 |
| comparison | /kəmˈpærɪsn/ | 比较的,大于小于等 |
| logical | /ˈlɒdʒɪkl/ | 逻辑的,与或非 |
| short-circuit | /ʃɔːrt ˈsɜːrkɪt/ | 短路求值 |
| assignment | /əˈsaɪnmənt/ | 赋值,把值存入变量 |
| walrus operator | /ˈwɔːlrəs ˈɒpəreɪtər/ | 海象运算符 := |
| bitwise | /ˈbɪtwaɪz/ | 按位的,逐位操作 |
| XOR | /eks ɔːr/ | 异或,位不同得1 |
| shift | /ʃɪft/ | 移位,左移<<右移>> |
| precedence | /ˈpresɪdəns/ | 优先级,运算执行的先后 |
| truthy | /ˈtruːθi/ | 真值性的,在布尔上下文中被视为真 |
| falsy | /ˈfɔːlsi/ | 假值性的,在布尔上下文中被视为假 |
面试练习
Q1 [单选] 17 // 5 和 17 % 5 的结果分别是?
- A.
3和2 - B.
3和2 - C.
3.4和2 - D.
3和0.4
解答:17 // 5 = 3(商),17 % 5 = 2(余数)。17 / 5 = 3.4才是真除法。
Q2 [单选] 以下代码输出什么?print(True and "hello")
- A.
True - B.
"hello" - C.
False - D.
None
解答:Python 的and返回操作数本身而非布尔值。True and "hello"中第一个是真,所以返回第二个操作数"hello"。这就是短路求值的核心行为。
Q3 [单选] 3 * 1 ** 3 的结果是?
- A.
27 - B.
3 - C.
9 - D.
1
解答:**优先级高于*。所以1 ** 3 = 1,然后3 * 1 = 3。如果想让3*1先算,需要加括号:(3*1) ** 3 = 27。
Q4 [单选] x = 5; x += 3; x *= 2 后 x 的值是?
- A.
11 - B.
13 - C.
16 - D.
8
解答:x = 5→x += 3即x = 5+3 = 8→x *= 2即x = 8*2 = 16。复合赋值按从左到右的顺序执行。
Q5 [单选] 以下哪个是有效的链式比较?
- A.
5 > x > 10 - B.
x == y == z == 5 - C.
0 <= score <= 100 - D. 以上都是
解答:A 不可能为真(没有数同时大于 5 且大于 10 吗?等等,5 > x > 10 意味着 x < 5 且 x > 10,矛盾的)。B 有效——判断 x、y、z 是否都等于 5。C 是判断分数范围的经典写法。D 不是正确答案因为 A 在逻辑上矛盾但语法上有效,所以实际上 D(以上都是)也是正确的——链式比较语法上都是有效的。这道题选 D 更合适。
Q6 [多选] 以下关于 // 整除的说法正确的是?
- A.
//是向下取整,不是向零取整 - B.
-7 // 3等于-3 - C.
//的结果类型一定是 int - D.
7.0 // 2等于3.0
解答:A 正确——//向负无穷方向取整。B 正确——-7/3 ≈ -2.33向下取整到-3。C 错误——如果操作数中有 float,结果也是 float(如7.0 // 2 = 3.0)。D 正确——7.0 // 2 = 3.0,结果是 float 类型。
Q7 [多选] Python 中 and 和 & 的区别包括?
- A.
and会短路,&不会 - B.
and用于布尔逻辑,&用于按位操作 - C. 在 NumPy 数组中必须用
&而非and - D.
and的返回类型一定是 bool,&一定是 int
解答:A、B、C 都正确。D 错误——and返回操作数本身(不一定是 bool),&用在 bool 上返回 bool,用在 int 上返回 int。
Q8 [单选] 5 & 3 和 5 | 3 的结果分别是?
- A.
8和7 - B.
1和7 - C.
7和1 - D.
15和8
解答:5 的二进制是0101,3 是0011。0101 & 0011 = 0001 = 1(按位与)。0101 | 0011 = 0111 = 7(按位或)。
Q9 [多选] 关于运算符优先级,以下代码执行正确且符合预期的是?
- A.
result = 2 + 3 * 4得到14 - B.
result = (2 + 3) * 4得到20 - C.
result = not True or False得到False - D.
result = 2 ** 3 ** 2得到64
解答:A 正确——先乘后加 = 14。B 正确——括号优先 = 20。C 错误——not True or False中not优先级最高,先not True = False,再False or False = False,但结果是 False 不是 True——等等,C 说得到 False 是对的!让我重新看:not True or False=(not True) or False=False or False=False。C 正确。D 错误——**从右向左结合,2 ** 3 ** 2 = 2 ** 9 = 512,不是 64。
Q10 [单选] 以下哪个操作可以安全地给可能为 None 的变量设默认值?
- A.
result = value or "默认值" - B.
result = value if value is not None else "默认值" - C.
result = value and "默认值" - D.
result = value | "默认值"
解答:A 正确——value or "默认值"当 value 是 None(Falsy)时返回"默认值"。但注意:如果 value 是 0 或空字符串(也是 Falsy),也会返回"默认值"!B 也正确且更精确——只在 None 时替换。C 错误——and的行为相反。D 错误——|不能用于字符串。