运算符与表达式

变量装着数据,运算符让数据”流动起来”。上一篇 变量与基本数据类型 我们介绍了 Python 有哪些基本类型,这一篇要把 Python 的七大类运算符一次讲完:算术、比较、赋值、逻辑、位运算、成员运算、身份运算。除此之外,还要讲清楚优先级、短路求值、海象运算符这些初学者很容易忽略但一旦不懂就翻车的细节。

一、算术运算符

Python 的算术运算符除了常规的加减乘除,还有一些特色:

1
2
3
4
5
6
7
8
9
print(7 + 3)      # 10   加
print(7 - 3) # 4 减
print(7 * 3) # 21 乘
print(7 / 3) # 2.3333333333333335 除(结果为 float)
print(7 // 3) # 2 整除(向下取整)
print(7 % 3) # 1 取余
print(7 ** 3) # 343 幂运算 7^3
print(-7 // 3) # -3!负数整除向下取整,不是向零截断
print(-7 % 3) # 2 结果与除数同号

有两个 需要特别小心的点

  1. /// 的区别/ 总是返回浮点数,即使能整除;// 是整除,结果向下取整;
  2. 负数取余-7 % 3 = 2,因为 Python 保证 (a // b) * b + a % b == a,所以余数与除数同号。这跟 C/Java 不一样,从 C 转来的同学容易踩坑。

字符串和列表也能用部分算术运算符:

1
2
3
4
print("ab" + "cd")     # abcd  拼接
print("-" * 10) # ----------
print([1, 2] + [3]) # [1, 2, 3]
print([0] * 5) # [0, 0, 0, 0, 0]

二、比较运算符

比较运算符返回 TrueFalse

1
2
3
4
5
print(3 < 5)       # True
print(3 <= 3) # True
print(3 > 5) # False
print(3 == 3) # True
print(3 != 5) # True

Python 支持链式比较(很多语言没有),是很 Pythonic 的语法:

1
2
3
4
5
6
7
age = 25
if 18 <= age < 60:
print("成年人")

# 等价于但不推荐
if age >= 18 and age < 60:
print("成年人")

链式比较不仅可读性好,还只求值一次 age,效率更高。

三、赋值运算符

除了普通的 =,Python 还有复合赋值

1
2
3
4
5
6
x = 10
x += 5 # x = x + 5 → 15
x -= 3 # x = x - 3 → 12
x *= 2 # x = x * 2 → 24
x //= 5 # x = x // 5 → 4
x **= 2 # x = x ** 2 → 16

海象运算符 :=(Python 3.8+)

海象运算符可以在表达式中赋值,非常适合”边算边判断”的场景:

1
2
3
4
5
6
7
8
9
# 传统写法
line = input()
while line != "quit":
print(f"你说:{line}")
line = input()

# 用海象运算符
while (line := input()) != "quit":
print(f"你说:{line}")

再比如读取文件:

1
2
3
4
5
6
7
8
9
# 传统写法
data = f.read(1024)
while data:
process(data)
data = f.read(1024)

# 海象写法
while data := f.read(1024):
process(data)

海象运算符不是必需的,但合理使用能让代码短一截。

四、逻辑运算符

Python 用英文单词 andornot,而不是 &&||!

1
2
3
4
5
6
7
8
9
10
11
age = 20
has_ticket = True

if age >= 18 and has_ticket:
print("可以进场")

if age < 12 or age > 65:
print("儿童或老人票")

if not has_ticket:
print("没票别想混进来")

短路求值

andor 支持短路:

  • A and BA 为假则不计算 B,直接返回 A
  • A or BA 为真则不计算 B,直接返回 A

利用这一点可以写出简洁代码:

1
2
3
4
5
# 提供默认值
name = user_input or "匿名用户"

# 防御性访问
result = data and data.get("value") # data 是 None 就不 .get()

注意and / or 返回的不一定是 True/False,而是参与运算的操作数本身

1
2
3
4
5
print(3 and 5)      # 5
print(0 and 5) # 0
print(3 or 5) # 3
print(0 or 5) # 5
print(None or "默认") # "默认"

需要严格 True/False 时,用 bool(x) 或写 not not x

五、位运算符

位运算把整数看作二进制串来操作:

1
2
3
4
5
6
7
8
9
a = 0b1100    # 12
b = 0b1010 # 10

print(bin(a & b)) # 0b1000 按位与
print(bin(a | b)) # 0b1110 按位或
print(bin(a ^ b)) # 0b0110 按位异或
print(bin(~a)) # -0b1101 按位取反
print(bin(a << 2)) # 0b110000 左移 2 位(相当于 *4)
print(bin(a >> 1)) # 0b110 右移 1 位(相当于 //2)

日常业务代码用不到位运算,但在性能优化、算法竞赛、协议解析、权限位图里非常重要。

一个实用例子——权限系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
READ    = 0b001    # 1
WRITE = 0b010 # 2
EXECUTE = 0b100 # 4

# 一个用户拥有 读+写 权限
user_perm = READ | WRITE # 0b011 = 3

# 检查是否有读权限
if user_perm & READ:
print("可读")

# 授予执行权限
user_perm |= EXECUTE # 0b111 = 7

# 撤销写权限
user_perm &= ~WRITE # 0b101 = 5

六、成员运算符 in / not in

判断元素是否在容器里:

1
2
3
4
print(3 in [1, 2, 3])          # True
print("a" in "banana") # True
print("apple" in {"apple": 1}) # True,判断字典的 key
print(5 not in {1, 2, 3}) # True

in 对不同容器复杂度不同:

  • list/tuple:O(n),逐个比较;
  • set/dict:O(1) 平均,走哈希;
  • str:O(n),子串搜索。

所以做大量”是否存在”检查时用 setdict 的 key 更快。

七、身份运算符 is / is not

is 判断两个变量是不是指向同一个对象

1
2
3
4
5
6
7
a = [1, 2, 3]
b = a
c = [1, 2, 3]

print(a is b) # True,a 和 b 是同一个列表
print(a is c) # False,值相同但不是同一个
print(a == c) # True,值相等

最常见的正确用法是判断 None

1
2
if result is None:
print("没有结果")

None 全局只有一个对象,用 is 判断既高效又语义清晰。不要is 判断字符串或数字相等,那会因为 CPython 的缓存优化产生奇怪的结果。

八、运算符优先级

Python 运算符优先级从高到低(简化版):

  1. **
  2. +x-x~x 一元
  3. *///%
  4. +-
  5. <<>>
  6. &
  7. ^
  8. |
  9. 比较运算符 <, <=, >, >=, !=, ==, in, is
  10. not x
  11. and
  12. or
  13. := 海象

背这张表没意义——记两件事就够了:

  1. 不确定时加括号
  2. not 的优先级高于 and/or,所以 not a == bnot (a == b),不是 (not a) == b
1
2
3
4
5
6
7
# 别写这种代码:
if a > 3 and b < 5 or c == 2 and not d:
...

# 请写这种:
if (a > 3 and b < 5) or (c == 2 and not d):
...

九、条件表达式(三元运算符)

Python 有一种紧凑的三元表达式:

1
2
3
4
5
6
7
8
# 传统写法
if age >= 18:
label = "成年"
else:
label = "未成年"

# 三元表达式写法
label = "成年" if age >= 18 else "未成年"

顺序和 C 的 ? : 不同,Python 是 “值1 if 条件 else 值2“,读起来更像英语。

嵌套三元虽然可行,但可读性会崩,超过一层建议改回 if

1
2
# 不推荐
grade = "A" if s >= 90 else "B" if s >= 80 else "C" if s >= 70 else "D"

十、一个综合例子:BMI 计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# bmi.py
# 综合运用运算符与表达式

def classify_bmi(bmi: float) -> str:
"""根据 BMI 返回体重分类。"""
if bmi < 18.5:
return "偏瘦"
elif bmi < 24:
return "正常"
elif bmi < 28:
return "超重"
else:
return "肥胖"


def main() -> None:
try:
weight = float(input("请输入体重(kg):"))
height = float(input("请输入身高(m):"))
except ValueError:
print("请输入数字,程序退出")
return

if not (0 < weight < 500 and 0.5 < height < 2.7):
print("身高/体重不合理,程序退出")
return

bmi = weight / height ** 2
print(f"你的 BMI 是 {bmi:.2f},属于「{classify_bmi(bmi)}」")


if __name__ == "__main__":
main()

这段代码里我们用了:

  • 算术运算符 /**
  • 链式比较 0 < weight < 500
  • 逻辑运算符 andnot
  • 类型转换 float()
  • 条件表达式(在 classify_bmi 中虽然是 if/elif,但你可以自己改成三元链);
  • f-string 格式化 :.2f(保留两位小数)。

十一、常见陷阱

  1. 整数除法陷阱:Python 3 里 10 / 33.333...,不是 3
  2. 浮点比较0.1 + 0.2 == 0.3 是 False,用 math.isclose
  3. and/or 返回值3 or 53,不是 True;需要布尔用 bool()
  4. not 优先级not a == bnot (a == b) 一样,不要误以为是 (not a) == b
  5. is 判断值x is 100 有时候巧合是 True,但不可靠,一律用 ==(或专门判 None);
  6. 链式赋值可变对象a = b = [] 让 a 和 b 都指向同一个空列表,appen 一个 a 都会变;
  7. ==is 对字符串"abc" is "abc" 在 CPython 里通常 True(因为字符串驻留),但不要依赖它。

十二、小结与延伸阅读

  • 记住 /(浮点除)、//(整除)、%(取余)、**(幂)的区别;
  • 链式比较 a < b < c 是 Python 特有的漂亮写法;
  • and/or 短路求值,返回操作数本身而非布尔;
  • 海象运算符 := 简化”边算边判断”;
  • 位运算在权限位、性能优化里很有用;
  • is 只用来判断 None 和对象身份;
  • 遇到复杂表达式记得加括号;
  • 条件表达式 A if cond else B 是 Python 里的三元运算符。

延伸阅读:

下一篇 字符串基础与常用方法 我们会深入讲字符串——Python 中用得最多的类型之一。