循环控制:break、continue、else

上一篇 循环语句 for 与 while 讲了 for/while 的基础,但真实项目里的循环往往需要动态控制——“某个条件满足时立刻退出”、”这一轮跳过不处理”、”循环正常结束后跑一段收尾”。这就是 break / continue / else 三件套的用武之地。这一篇把它们的语法、语义、惯用法、陷阱一次讲清,特别是 for-else 这个 Python 独有的语法糖,被无数新手忽视,其实很好用。

一、break:立刻退出循环

break 中断最近一层的循环,控制流跳到循环体后的第一条语句:

1
2
3
4
5
# 找出第一个负数
for n in [3, 5, -1, 7, -2]:
if n < 0:
print(f"找到 {n}")
break # 立刻退出,后面 7 和 -2 不再看

break 只跳最近一层,嵌套循环里想跳到最外层需要额外手段(下面讲)。

二、continue:跳过本次迭代

continue 结束本次迭代,直接进入下一轮循环开头(for 会取下一个元素,while 会重新判断条件):

1
2
3
4
5
# 只打印偶数
for n in range(10):
if n % 2 != 0:
continue
print(n)

注意continue 不是 break 的近亲,它不会退出循环,只是跳过剩余的循环体。

continue 与 else if 的区别

1
2
3
4
5
6
7
8
9
10
# 用 continue(Guard 式)
for n in numbers:
if not is_valid(n):
continue
process(n) # 只有合法元素才处理

# 不用 continue
for n in numbers:
if is_valid(n):
process(n)

两种写法功能等价,但当验证逻辑很多时(比如 5 层校验),continue 让代码扁平化:

1
2
3
4
5
6
7
8
9
10
11
12
for order in orders:
if not order.paid:
continue
if order.refunded:
continue
if not order.address:
log("no address")
continue
if order.amount <= 0:
continue
# 到这里说明这是一个可以处理的订单
ship(order)

比嵌套 4 层 if 好读得多。

三、else:循环正常结束后的收尾

for-elsewhile-else 是 Python 特色:当循环因为”迭代完成”而结束(没被 break 打断)时,执行 else 块。

1
2
3
4
5
6
for n in [3, 5, 7, 11]:
if n % 2 == 0:
print("发现偶数")
break
else:
print("全都是奇数") # 只有循环跑完(没 break)才执行

理解要点:else 不是配 if 的,是配 for/while 的。语义可以读成”loop nobreak”—— 循环没被打断时才执行。

常见惯用法:搜索”是否存在”

1
2
3
4
5
6
7
def find(items, target):
for i, x in enumerate(items):
if x == target:
print(f"找到 {target} 在索引 {i}")
break
else:
print(f"{target} 不存在")

for-else 让”未找到”分支非常自然。不用它就得:

1
2
3
4
5
6
7
found = False
for i, x in enumerate(items):
if x == target:
found = True
break
if not found:
print("不存在")

多一个 flag 变量,明显啰嗦。

while-else

while 也支持 else:条件变为假时执行 else(等同”没被 break”)。

1
2
3
4
5
6
7
8
9
attempts = 0
while attempts < 3:
pwd = input("密码:")
if pwd == "123456":
print("登录成功")
break
attempts += 1
else:
print("三次输错,账户已锁定")

四、跳出嵌套循环的四种姿势

break 只跳一层。想跳多层,四种方案:

方案 1:抽成函数 + return(推荐)

1
2
3
4
5
6
7
8
9
def find(matrix, target):
for row in matrix:
for cell in row:
if cell == target:
return True
return False

if find(m, 42):
print("找到")

这是最 Pythonic 的做法。别的方案都是它的降级替代。

方案 2:flag 变量

1
2
3
4
5
6
7
8
found = False
for row in matrix:
for cell in row:
if cell == target:
found = True
break
if found:
break

多层 flag 会越写越乱,慎用。

方案 3:抛异常

1
2
3
4
5
6
7
8
9
10
class _Found(Exception):
pass

try:
for row in matrix:
for cell in row:
if cell == target:
raise _Found
except _Found:
print("找到")

用异常做流控被认为是反模式,除非嵌套非常深,否则不建议。

方案 4:把嵌套改成扁平

itertools.product 或者展开成单层:

1
2
3
from itertools import product
for row, cell in product(matrix, matrix[0]):
...

五、continue 在 while 里的坑

在 while 里用 continue 时,别忘了更新循环变量,否则死循环:

1
2
3
4
5
6
i = 0
while i < 10:
if i == 5:
continue # ❌ i 没变,永远卡在 5!
print(i)
i += 1

正确写法:

1
2
3
4
5
6
7
i = 0
while i < 10:
if i == 5:
i += 1
continue
print(i)
i += 1

for 循环用 continue 就没这个坑,因为迭代器自动”下一步”。所以能用 for 就别用 while。

六、for-else 到底该不该用?

有人觉得 for-else 让代码更简洁,也有人(包括 Guido 本人)承认这个关键字选得不好——else 语义不直观。原本更贴切的名字是 nobreak。用不用这个语法,业界有两派:

  • 鼓励派:写搜索/检查场景时优雅,减少 flag 变量;
  • 保守派:容易被误读为”if-else 的 else”,代码 review 时增加认知负担。

折中建议

  • 团队里所有人都懂 → 大胆用;
  • 有人不熟 → 加一行注释 # 循环没 break
  • 或者干脆抽成函数,用 return 表达”提前找到”。

七、一个综合例子:账户登录

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# login.py
"""控制台登录模拟,演示循环控制三件套。"""

USERS = {
"admin": "admin888",
"alice": "alice123",
"bob": "bob@2025",
}

MAX_ATTEMPTS = 3


def login() -> bool:
for attempt in range(1, MAX_ATTEMPTS + 1):
user = input(f"用户名(第 {attempt} 次):").strip()
if not user:
print("用户名不能为空")
continue # 空的直接下一轮,不占尝试次数?—— 这里演示占用

if user not in USERS:
print("用户不存在")
continue

pwd = input("密码:")
if pwd == USERS[user]:
print(f"欢迎,{user}")
return True
else:
print(f"密码错误,还剩 {MAX_ATTEMPTS - attempt} 次机会")
else:
# 循环正常跑完(没 return / break)→ 三次都错
print("三次尝试失败,账户已锁定 5 分钟")
return False


def command_loop():
print("输入命令(quit 退出):")
while True:
cmd = input("> ").strip()
if not cmd:
continue # 空行忽略
if cmd in {"quit", "exit"}:
print("再见")
break
if cmd == "help":
print("可用命令:help, status, quit")
continue
print(f"未识别命令 {cmd!r}")


if __name__ == "__main__":
if login():
command_loop()

这段代码里同时用了 break(退出命令循环)、continue(跳过空行/错误用户名)、for-else(三次失败后锁定)、return(登录成功直接跳出函数)。

八、常见陷阱

陷阱 1:break 与 return 混用

在函数里,能用 return 就用 return,比 break 更”干脆”:

1
2
3
4
5
def find(items, target):
for i, x in enumerate(items):
if x == target:
return i # ✅ 直接返回,比 break 后 return 简洁
return -1

陷阱 2:for-else 里没 break

1
2
3
4
for x in items:
print(x)
else:
print("done") # 一定会执行,跟没 else 一样

如果循环里没有 break,else 块是必然执行的,等价于把它写在循环后面——但读代码的人会疑惑”为啥用 else”。没 break 就别写 else

陷阱 3:continue 后写代码

1
2
3
4
5
for x in items:
if x < 0:
continue
print("负数") # 永远执行不到
print(x)

continue 后面的代码是死代码,多数 linter 会警告。

陷阱 4:break 跳不出嵌套函数

1
2
3
4
5
6
def outer():
for x in items:
def inner():
for y in items:
break # 只跳出 inner 的循环,不影响 outer
inner()

break 只在当前函数的最近一层循环生效。

陷阱 5:while True 忘了退出条件

1
2
3
while True:
cmd = input()
print(cmd) # 永远不退出

while True 一定要有 breakreturnraise,否则就是死循环。

九、性能小提示

  • 循环开销:CPython 里循环开销比 C 大得多,能用内置函数就别自己写循环:
    • 累加:sum(nums)for + += 快;
    • 找最大:max(nums)
    • 存在性:any(x > 0 for x in nums)all(x > 0 for x in nums)
    • 一次性构造 list:列表推导式比 append 循环快 30%+。
  • break 早退能避免不必要的工作,尤其是数据量大时。

十、小结与延伸阅读

  • break 退出最近一层循环;
  • continue 跳过本次迭代;
  • else 在循环没被 break 时执行;
  • 嵌套循环退出优先用函数 + return;
  • while + continue 记得手动更新循环变量;
  • 没 break 别写 else;
  • 能用 sum/max/any/all/推导式就别写手工循环。

延伸阅读:

到这里 模块一:Python 基础入门 10 篇 就全部结束了!从下一篇开始进入 模块二:核心数据结构,第一站是 列表 List 详解

循环语句 for 与 while

条件语句让程序”做选择”,循环语句让程序”重复干活”。上一篇 条件语句 if/elif/else 讲完了选择,这一篇要讲 Python 里两种循环:forwhile。Python 的 for 循环跟 C/Java 完全不一样——它没有”变量 i 从 0 到 n”这种写法,而是”取出一个可迭代对象里的每个元素”。理解这个差异,你写循环的水平能直接跨一个台阶。

一、for:遍历可迭代对象

for 的基础形式:

1
2
for 变量 in 可迭代对象:
循环体

举例:

1
2
3
4
5
6
7
8
for name in ["咖飞", "小王", "小李"]:
print(f"你好,{name}")

for ch in "Python":
print(ch)

for key in {"a": 1, "b": 2}: # 遍历字典默认遍历 key
print(key)

Python 的 for 不是”计数器循环”,它是”迭代器循环”。任何可迭代对象都能放在 in 后面:列表、元组、字典、集合、字符串、文件、生成器……

二、range:生成数字序列

想让 for 循环 n 次?用 range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(5):
print(i)
# 0 1 2 3 4

for i in range(1, 6): # [1, 6)
print(i)
# 1 2 3 4 5

for i in range(0, 10, 2): # start=0, stop=10, step=2
print(i)
# 0 2 4 6 8

for i in range(10, 0, -1):
print(i)
# 10 9 8 7 6 5 4 3 2 1

range(n) 不会立即生成一个 n 元素列表,而是懒生成——只在迭代时按需产生下一个数字。range(10 ** 9) 也几乎不占内存。

如果你确实要一个列表:list(range(10))[0, 1, ..., 9]

三、enumerate:需要索引时的正确姿势

新手写法(不推荐):

1
2
3
names = ["咖飞", "小王", "小李"]
for i in range(len(names)):
print(i, names[i])

Pythonic 写法

1
2
for i, name in enumerate(names):
print(i, name)

enumerate 会同时给你索引和元素。可以指定起始编号:

1
2
for i, name in enumerate(names, start=1):
print(f"第 {i} 名:{name}")

四、zip:并行遍历多个序列

1
2
3
4
5
names = ["咖飞", "小王"]
ages = [3, 20]

for name, age in zip(names, ages):
print(f"{name}{age} 岁")

zip 会以最短的序列为准,多出来的会被丢弃:

1
list(zip([1, 2, 3], ["a", "b"]))    # [(1, 'a'), (2, 'b')]

想以最长为准并补默认值:itertools.zip_longest

五、遍历字典

1
2
3
4
5
6
7
8
9
10
11
12
13
score = {"张三": 90, "李四": 85, "王五": 78}

# 只要 key
for name in score:
print(name)

# 要 key 也要 value
for name, s in score.items():
print(name, s)

# 只要 value
for s in score.values():
print(s)

六、while:条件循环

1
2
3
4
count = 0
while count < 5:
print(count)
count += 1

while 的关键:每次迭代前判断条件,为真才进入循环体。所以你要确保条件能变为假,否则死循环。

死循环有时是故意的——比如服务端:

1
2
3
4
5
while True:
request = get_next_request()
if request is None:
break
handle(request)

想优雅地”读到 EOF 结束”:

1
2
while (line := input()):     # 海象运算符
process(line)

七、for 还是 while?

  • 知道要循环的对象/次数 → for;
  • 循环退出取决于某个条件 → while。
1
2
3
4
5
6
7
8
9
10
# for:处理一批固定数据
for user in users:
send_email(user)

# while:一直读,读到"quit"退出
while True:
cmd = input()
if cmd == "quit":
break
handle(cmd)

八、break / continue / else

  • break:立刻退出循环;
  • continue:跳过本次迭代,进入下一次;
  • else循环正常结束(没被 break)时执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 找第一个偶数
for n in [1, 3, 5, 4, 7]:
if n % 2 == 0:
print(f"找到 {n}")
break
else:
print("没找到")

# continue:跳过负数
for n in [3, -1, 5, -2, 8]:
if n < 0:
continue
print(n)

for-else / while-else 是 Python 特色,其他语言基本没有。注意 else 不是配对 if 的,而是配对 for/while 的

1
2
3
4
5
6
7
8
# 判断质数
n = 17
for i in range(2, n):
if n % i == 0:
print(f"{n} 不是质数")
break
else:
print(f"{n} 是质数")

九、循环控制的深入讲解

关于 break、continue、else 的更多细节和陷阱,我们下一篇 循环控制:break、continue、else 会单独讲。

十、嵌套循环

1
2
3
4
5
# 九九乘法表
for i in range(1, 10):
for j in range(1, i + 1):
print(f"{j}*{i}={i*j}", end="\t")
print()

跳出嵌套循环break 只跳出最近的一层。要跳多层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方式 1:用 flag
found = False
for row in matrix:
for cell in row:
if cell == target:
found = True
break
if found:
break

# 方式 2:抽成函数,用 return
def find(matrix, target):
for row in matrix:
for cell in row:
if cell == target:
return True
return False

方式 2 更 Pythonic,几乎所有多层循环都可以用”抽函数 + return”替代 flag。

十一、循环的效率优化

1. 避免在循环里重复计算

1
2
3
4
5
6
7
# ❌ 每次循环都调用 len()
for i in range(len(a)):
...

# ✅ 更好的是能不用索引就不用
for x in a:
...

2. 用 in / set 而不是循环查找

1
2
3
4
5
6
7
8
9
# ❌ O(n)
def contains(items, target):
for x in items:
if x == target:
return True
return False

# ✅ 一步搞定
target in items

3. 用生成器代替中间列表

1
2
3
4
5
# ❌ 生成 100 万个中间元素
sum([x*x for x in range(10**6)])

# ✅ 生成器表达式,边算边加
sum(x*x for x in range(10**6))

十二、常用循环模式

累加

1
2
3
4
total = 0
for x in numbers:
total += x
# 等价:total = sum(numbers)

收集

1
2
3
4
5
result = []
for x in items:
if x > 0:
result.append(x * 2)
# 等价:result = [x*2 for x in items if x > 0](列表推导式,第 15 篇讲)

分组计数

1
2
3
4
counter = {}
for w in words:
counter[w] = counter.get(w, 0) + 1
# 等价:from collections import Counter; Counter(words)

早期退出

1
2
3
4
5
def find_first_negative(nums):
for n in nums:
if n < 0:
return n
return None

十三、一个综合例子:素数筛选

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
# primes.py
"""筛选出 2..N 之间的所有质数(埃拉托色尼筛法)。"""

def sieve(n: int) -> list[int]:
if n < 2:
return []
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False

for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
# 从 i*i 开始把 i 的倍数标为合数
for j in range(i * i, n + 1, i):
is_prime[j] = False

return [i for i, prime in enumerate(is_prime) if prime]


def main():
n = int(input("求 2 到 N 之间的所有质数,请输入 N:"))
primes = sieve(n)
print(f"共 {len(primes)} 个质数:")
# 每行 10 个
for i, p in enumerate(primes):
print(f"{p:5d}", end="\n" if (i + 1) % 10 == 0 else " ")
print()


if __name__ == "__main__":
main()

这段代码用到了:for、range、双层循环、break/continue/else 的思想(虽然本例没显式用)、列表推导式、enumerate、f-string 对齐格式化。

十四、常见陷阱

陷阱 1:循环中修改被遍历的列表

1
2
3
4
5
items = [1, 2, 3, 4, 5]
for x in items:
if x % 2 == 0:
items.remove(x) # 危险!索引错乱
print(items) # 可能不是你想的

正确做法:遍历副本,或者构造新列表

1
2
items = [x for x in items if x % 2]   # 保留奇数
# 或者:for x in items[:]: 遍历副本

陷阱 2:变量作用域

1
2
3
for i in range(3):
pass
print(i) # 输出 2,i 泄漏到外层作用域

Python 的 for 变量不局限在循环体内,这跟 C/Java 不同。避免依赖循环变量在循环外的值

陷阱 3:range 的性能误区

for i in range(len(a)) 这种写法性能其实还行,但可读性差。能不用索引就不用

陷阱 4:字典遍历时修改

1
2
3
4
d = {"a": 1, "b": 2}
for k in d:
if d[k] == 1:
del d[k] # RuntimeError: dictionary changed size

list(d.items()) 拷一份再遍历,或者用字典推导式重建。

陷阱 5:忘了 int() 转型

1
2
3
n = input("次数:")
for i in range(n): # TypeError,n 是字符串
...

for i in range(int(n)): 才对。

十五、小结与延伸阅读

  • for 是”遍历可迭代对象”,不是”计数器循环”;
  • 需要索引用 enumerate,并行多序列用 zip
  • while 用于”条件驱动的循环”;
  • break/continue/else 是循环控制三件套;
  • 跳出多层循环用函数 + return,别写 flag;
  • 循环中不要修改被遍历的容器;
  • 常见循环模式(累加、收集、计数、找第一个)都有标准库替代品,能不写循环就不写。

延伸阅读:

下一篇 循环控制:break、continue、else 我们要把 break/continue/else 的细节和惯用法讲透。

条件语句 if/elif/else

程序的第一层复杂性来自”做选择”:判断用户是不是成年、订单是不是超时、密码是不是正确……这些都需要条件语句。Python 用 if / elif / else 完成这件事,同时还有一个 Python 3.10+ 的现代化补充:match/case(模式匹配)。这一篇把两种写法都讲透,让你知道什么时候用什么。

一、if 的基本形式

Python 条件语句最基本的形式:

1
2
3
4
age = int(input("年龄:"))

if age >= 18:
print("成年")

注意四件事:

  1. if 后面跟条件,条件后紧跟一个冒号 :
  2. 缩进 4 空格表示”属于 if 的代码块”;
  3. 条件不需要括号(if (age >= 18): 语法上合法但不推荐);
  4. 不用大括号,缩进就是分隔。

二、if / else

选择的两条分支:

1
2
3
4
5
6
age = int(input("年龄:"))

if age >= 18:
print("成年")
else:
print("未成年")

三、if / elif / else

超过两条分支时用 elif(else if 的缩写):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
score = int(input("分数:"))

if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"

print(f"等级 {grade}")
  • elif 可以有任意多个;
  • else 是可选的,最多一个;
  • 一旦某条分支匹配就退出,后续分支不再检查。

四、条件里的”真”与”假”

前面 变量与基本数据类型 讲过:Python 里以下都视为

  • FalseNone
  • 数值 00.0
  • 空字符串 ""、空列表 []、空字典 {}、空集合 set()

其他一切都是。所以:

1
2
3
4
5
6
7
8
9
10
name = input("名字:")

if name: # ✅ Pythonic
print(f"你好,{name}")
else:
print("你没输入名字")

# ❌ 冗长
if name != "":
print(...)

自定义对象可以通过 __bool____len__ 方法自定义真值行为,这个后面 特殊方法(魔术方法) 会讲。

五、复合条件:and / or / not

多个条件组合:

1
2
3
4
5
6
7
8
9
10
11
12
age = 25
has_ticket = True
is_vip = False

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

if is_vip or age >= 65:
print("免排队")

if not has_ticket:
print("买票先")

三个要点:

  1. and or 短路求值A and B 若 A 为假就不算 B;
  2. not 优先级 高于 and / or
  3. 复杂条件加括号更安全:if (a and b) or (c and not d):

六、链式比较(Python 特色)

1
2
3
4
5
6
7
8
9
age = 25

# 好,Python 特有
if 18 <= age < 60:
print("劳动年龄")

# 不好
if age >= 18 and age < 60:
print("劳动年龄")

七、嵌套 if

if 里可以再套 if

1
2
3
4
5
6
7
8
9
10
age = int(input("年龄:"))
has_id = input("有身份证吗?(y/n):") == "y"

if age >= 18:
if has_id:
print("可以办卡")
else:
print("请先办身份证")
else:
print("年龄不足")

嵌套超过 2 层就要考虑重构——通常改成”提前 return”或者合并条件:

1
2
3
4
5
6
7
# 更清爽
if age < 18:
print("年龄不足")
elif not has_id:
print("请先办身份证")
else:
print("可以办卡")

在函数里更常见 “early return“ 风格:

1
2
3
4
5
6
def apply_card(age: int, has_id: bool) -> str:
if age < 18:
return "年龄不足"
if not has_id:
return "请先办身份证"
return "可以办卡"

这种”守卫式返回”能显著降低嵌套复杂度。

八、条件表达式(三元)

前面 运算符与表达式 讲过:

1
2
3
4
label = "成年" if age >= 18 else "未成年"

# 常用于生成列表元素
tags = ["young" if a < 18 else "adult" for a in ages]

九、match / case:Python 3.10+ 的模式匹配

match 是 Python 3.10 引入的重量级语法,功能比 C/Java 的 switch 强得多。它匹配的不是”值”,而是”结构”。

基础用法(当增强版 switch)

1
2
3
4
5
6
7
8
9
10
11
12
def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 301 | 302: # 或匹配
return "Redirect"
case 400 | 401 | 403 | 404:
return "Client Error"
case 500:
return "Server Error"
case _: # 通配,等价于 default
return "Unknown"

case _ 是通配符,一定放在最后,作用等同于 else

结构匹配(真正强大的地方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def describe_point(point: tuple) -> str:
match point:
case (0, 0):
return "原点"
case (0, y):
return f"Y 轴上,y={y}"
case (x, 0):
return f"X 轴上,x={x}"
case (x, y):
return f"点 ({x}, {y})"
case _:
return "不是坐标"

print(describe_point((0, 0))) # 原点
print(describe_point((5, 0))) # X 轴上,x=5
print(describe_point((3, 4))) # 点 (3, 4)

匹配字典 / 类

1
2
3
4
5
6
7
8
9
10
11
12
13
def handle_event(event: dict) -> str:
match event:
case {"type": "click", "x": x, "y": y}:
return f"点击 ({x}, {y})"
case {"type": "keydown", "key": key}:
return f"按键 {key}"
case {"type": t}:
return f"未知事件 {t}"
case _:
return "格式不对"

print(handle_event({"type": "click", "x": 10, "y": 20})) # 点击 (10, 20)
print(handle_event({"type": "keydown", "key": "Enter"})) # 按键 Enter

带守卫的 case

1
2
3
4
5
6
7
8
9
10
def category(age: int) -> str:
match age:
case n if n < 0:
return "非法"
case n if n < 18:
return "未成年"
case n if n < 60:
return "成年"
case _:
return "老人"

match vs if-elif

  • 简单值比较:if-elif 就够,match 反而啰嗦;
  • 结构匹配(元组解构、字典模式、类模式):match 优雅得多;
  • 顺序执行:match 只匹配第一个符合的 case,且不会 fall through(不用写 break)。

Python 3.9 及更早版本不能用 match,必须用 if-elif。

十、一个综合例子:石头剪刀布

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# rps.py
"""石头剪刀布,展示 if 与 match 的组合。"""

import random


def get_computer_choice() -> str:
return random.choice(["石头", "剪刀", "布"])


def get_user_choice() -> str:
while True:
raw = input("请出招(石头/剪刀/布/quit):").strip()
if raw == "quit":
return raw
if raw in {"石头", "剪刀", "布"}:
return raw
print("请输入正确选项")


def judge(user: str, computer: str) -> str:
if user == computer:
return "平局"
# 用 match 表达"什么克什么"
match user:
case "石头":
return "赢" if computer == "剪刀" else "输"
case "剪刀":
return "赢" if computer == "布" else "输"
case "布":
return "赢" if computer == "石头" else "输"
case _:
return "非法"


def main():
score = {"赢": 0, "输": 0, "平局": 0}
while True:
user = get_user_choice()
if user == "quit":
break
computer = get_computer_choice()
result = judge(user, computer)
score[result] += 1
print(f"电脑:{computer},结果:{result}")

print(f"总战绩:{score}")


if __name__ == "__main__":
main()

十一、常见陷阱

  1. 忘记冒号if age >= 18 忘写 : 会立即报 SyntaxError。
  2. 缩进不一致:Tab 混空格会报 IndentationError;建议 IDE 里配置”Tab = 4 空格”。
  3. if x == True:多此一举。直接 if x:
  4. if x == None:应该用 if x is None
  5. 在条件里赋值:Python 3.8 之前不允许,3.8 起用海象 :=,别把 === 搞混:if x = 5: 是语法错误,好事。
  6. elif 之间条件重叠if x > 10: ... elif x > 5: ... 中,只会命中第一个匹配的分支。
  7. match 里 case 常量名 陷阱case foo: 会把 foo 当作新绑定的变量名而不是常量。要匹配已有常量,加上模块前缀:case Color.RED: 或者比较值 case _ if x == FOO:
1
2
3
4
5
FOO = 1
def f(x):
match x:
case FOO: # 陷阱!FOO 被当成新变量,永远匹配
return "matched"

正确写法:

1
2
3
4
5
6
7
8
from enum import Enum
class Color(Enum):
RED = 1

def f(c):
match c:
case Color.RED: # 带点号的名字才被识别为常量匹配
return "red"

十二、小结与延伸阅读

  • 基本形式:if / elif / else,注意冒号和缩进;
  • 空容器、0、None 都是假;直接 if x: 判空最 Pythonic;
  • 链式比较 18 <= age < 60
  • 复杂条件加括号,避免优先级坑;
  • 嵌套超 2 层考虑 early return;
  • 三元表达式 A if cond else B
  • Python 3.10+ 用 match/case 做结构匹配;
  • match 里的常量必须带前缀,否则被当成变量绑定。

延伸阅读:

下一篇 循环语句 for 与 while 我们要让程序开始”重复干活”。

输入输出与格式化字符串

程序要想活起来,就得会跟外界说话。这一篇讲 Python 中最基础的输入输出:input()print(),以及新时代最优雅的字符串格式化方式 —— f-string。搞懂它们之后,你就能写出”接收用户输入 → 处理 → 打印结果”的完整脚本了。这看起来简单,但里面有很多”看起来会实际不会”的细节,比如 printsep/end/flush 参数、input 的类型陷阱、f-string 的高级用法等等。

一、print:不止是打印

先看最基础用法:

1
2
3
4
5
print("Hello, World!")
print("咖飞", "3 岁") # 多个参数,默认用空格分隔
print("咖飞", "3 岁", sep=" - ") # 自定义分隔符
print("行 1", end=" | ") # 不换行,用 " | " 结束
print("行 2") # 输出:行 1 | 行 2
  • *objects:要打印的对象(可以任意多个);
  • sep:多个对象之间的分隔符,默认为 ' '
  • end:结束字符,默认为 '\n'
  • file:输出到哪个文件对象,默认是 sys.stdout
  • flush:是否立刻刷新缓冲区,默认 False

输出到文件

1
2
with open("log.txt", "w", encoding="utf-8") as f:
print("日志内容", file=f)

with 会在退出块时自动关闭文件,具体见 with 语句与上下文管理器

输出到 stderr

错误信息应该输出到标准错误流,方便区分正常输出:

1
2
import sys
print("这是错误信息", file=sys.stderr)

flush 的作用

Python 默认是行缓冲(终端)或块缓冲(管道/文件),有时候你会发现输出没立刻显示:

1
2
3
4
5
6
import time

for i in range(5):
print(f"进度 {i}", end=" ")
time.sleep(1)
# 5 秒后一次性打印,因为没换行触发刷新

flush=True 就实时了:

1
2
3
for i in range(5):
print(f"进度 {i}", end=" ", flush=True)
time.sleep(1)

打印任意对象

print 会自动调用对象的 __str__ 方法把它转成字符串:

1
2
3
4
print([1, 2, 3])         # [1, 2, 3]
print({"a": 1, "b": 2}) # {'a': 1, 'b': 2}
print((1, 2)) # (1, 2)
print(None) # None

想调试对象内部结构时,repr 更详细:

1
2
3
s = "咖飞"
print(s) # 咖飞
print(repr(s)) # '咖飞' 带引号,能看出是字符串

调试大型嵌套数据用 pprint

1
2
3
from pprint import pprint
data = {"users": [{"name": "a"}, {"name": "b"}]}
pprint(data, width=40)

二、input:读取用户输入

1
2
name = input("请输入你的名字:")
print(f"你好,{name}")

三条关键规则:

  1. input() 永远返回字符串,即便你输入了数字;
  2. 输入的末尾不含换行符
  3. 参数是”提示语”(prompt),显示在光标前。

转类型

想要数字必须显式转换:

1
2
age = int(input("年龄:"))         # 输入 abc 会抛 ValueError
weight = float(input("体重:"))

永远要考虑用户会瞎输入

1
2
3
4
5
6
7
8
9
10
def read_int(prompt: str) -> int:
"""反复让用户输入直到输入合法整数。"""
while True:
raw = input(prompt).strip()
try:
return int(raw)
except ValueError:
print("请输入合法整数")

age = read_int("年龄:")

一次性读多个值

1
2
3
4
5
6
7
raw = input("请输入身高体重(用空格分隔):")   # 例:170 65
parts = raw.split()
if len(parts) != 2:
print("格式错误")
else:
height, weight = map(float, parts)
print(f"BMI: {weight / (height/100)**2:.2f}")

getpass:隐藏密码

密码不应该显示在终端上:

1
2
from getpass import getpass
pwd = getpass("请输入密码:") # 输入时不显示

三、f-string:格式化的正确姿势

f-string 是 Python 3.6+ 引入的字符串前缀 f,可以在花括号里直接嵌入表达式:

1
2
3
4
5
6
7
8
9
10
name = "咖飞"
age = 3
print(f"我叫 {name},今年 {age} 岁")

# 表达式也可以
print(f"明年 {age + 1} 岁")
print(f"名字长度 {len(name)}")

# 调用方法
print(f"大写:{name.upper()}")

格式说明符 :...

1
2
3
4
5
6
7
pi = 3.14159265
print(f"{pi:.2f}") # 3.14 保留 2 位小数
print(f"{pi:10.2f}") # ' 3.14' 总宽度 10,右对齐
print(f"{pi:<10.2f}") # '3.14 ' 左对齐
print(f"{pi:^10.2f}") # ' 3.14 ' 居中
print(f"{pi:>10.2f}") # ' 3.14' 右对齐(默认)
print(f"{pi:0>10.2f}") # '0000003.14' 填充 0

千分位

1
2
3
big = 1234567890
print(f"{big:,}") # 1,234,567,890 千分位分隔
print(f"{big:_}") # 1_234_567_890 下划线分隔

百分比

1
2
rate = 0.256
print(f"{rate:.1%}") # 25.6% 自动 *100 加 %

进制转换

1
2
3
4
5
6
7
n = 255
print(f"{n:b}") # 11111111 二进制
print(f"{n:o}") # 377 八进制
print(f"{n:x}") # ff 十六进制小写
print(f"{n:X}") # FF 十六进制大写
print(f"{n:#x}") # 0xff 带前缀
print(f"{n:08b}") # 11111111 补零到 8 位

日期时间

1
2
3
from datetime import datetime
now = datetime.now()
print(f"{now:%Y-%m-%d %H:%M:%S}") # 2025-09-05 09:12:38

调试神器 =(Python 3.8+)

1
2
3
4
x = 42
y = 10
print(f"{x=}, {y=}, {x*y=}")
# x=42, y=10, x*y=420

以前调试要写 print("x =", x),现在 f"{x=}" 一键搞定,是最实用的现代 Python 特性之一。

多行 f-string

1
2
3
4
5
6
7
name = "咖飞"
report = f"""
姓名:{name}
年龄:{3}
状态:{"在学习中" if True else "偷懒"}
"""
print(report)

花括号本身

想输出字面的 {},写两个:

1
2
print(f"{{}}")       # {}
print(f"字典字面:{{'a': 1}}") # 字典字面:{'a': 1}

四、格式化的历史三代

作为对比,看看 Python 里字符串格式化的演变:

1
2
3
4
5
6
7
8
9
10
11
name, age = "咖飞", 3

# 第 1 代:% (C printf 风格,Python 2 时代)
print("我叫 %s,%d 岁" % (name, age))

# 第 2 代:str.format() (Python 2.6+)
print("我叫 {},{} 岁".format(name, age))
print("我叫 {n},{a} 岁".format(n=name, a=age))

# 第 3 代:f-string (Python 3.6+) ← 推荐
print(f"我叫 {name}{age} 岁")

新代码请用 f-string。看到 %format() 一般是老代码。有一个例外:当格式串本身来自用户或配置文件时,就必须用 format(),因为 f-string 是编译期解析变量的。

1
2
3
# 从配置里读的模板
template = "Hello, {name}! Today is {date}"
print(template.format(name="咖飞", date="2025-09-05"))

五、一个综合小项目:ATM 提款机

我们把这一篇学到的东西糅到一起:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
# atm.py
"""极简 ATM 提款机演示 input/print/f-string 用法。"""

from getpass import getpass


def prompt_amount() -> float:
while True:
raw = input("请输入提款金额(元):").strip()
try:
amount = float(raw)
except ValueError:
print("请输入数字")
continue
if amount <= 0:
print("金额必须大于 0")
continue
if amount % 100 != 0:
print("金额必须是 100 的整数倍")
continue
return amount


def main():
balance = 8888.88
pwd = getpass("请输入 6 位密码:")
if pwd != "123456":
print("密码错误,卡已吞", flush=True)
return

print(f"欢迎,当前余额:¥{balance:,.2f}")
amount = prompt_amount()
if amount > balance:
print(f"余额不足!你想取 ¥{amount:,.2f},但只有 ¥{balance:,.2f}")
return

balance -= amount
print("=" * 30)
print(f"取款成功:¥{amount:,.2f}")
print(f"当前余额:¥{balance:,.2f}")
print(f"时间:{__import__('datetime').datetime.now():%Y-%m-%d %H:%M:%S}")
print("=" * 30)


if __name__ == "__main__":
main()

这段代码用到了:

  • input() 与循环校验;
  • getpass() 隐藏密码;
  • f-string 的多种格式(千分位、小数位、日期、宽度对齐);
  • print(sep, end, flush)
  • 函数(下一部分要讲)。

六、命令行参数(简版)

如果不想让程序停下来等 input(),而是直接在命令行传参:

1
2
3
# hello.py
import sys
print(f"参数:{sys.argv}")

运行:

1
2
python hello.py 咖飞 3
# 参数:['hello.py', '咖飞', '3']

更强大的命令行解析用 argparse(标准库)或 typer/click(第三方),我们后面单独讲。

七、常见陷阱

  1. input() 结果是字符串age = input()age + 1 报错,要 int(age) + 1
  2. print 中的 , 是分隔多个对象,不是字符串拼接。print("a", "b") 输出 a b,不是 ab。要拼接用 f-string 或 +
  3. f-string 里的引号f"{name.replace("a", "b")}" 在 Python 3.11 前会报错(引号打架)。用双引号包外层、单引号包里层,或反过来。
  4. flush 忘记加:进度条、心跳输出不显示,绝大多数是没加 flush=True
  5. 中文 Windows 终端乱码chcp 65001 切换 UTF-8,或者用 print(..., flush=True) 后再重定向。
  6. input 里的换行input() 不含结尾换行,如果需要按空行退出可以判断 if not line:
  7. f-string 不能格式化字典的引号 keyf"{d["key"]}" 引号冲突,改成 d['key']

八、小结与延伸阅读

  • printsep/end/file/flush 四个宝藏参数;
  • input() 只返回字符串,用完记得转类型;
  • 密码输入用 getpass
  • 新代码统一用 f-string,{value:.2f}{n:,}{now:%Y-%m-%d} 记熟;
  • 调试小技巧:f"{x=}"
  • 模板来自外部数据时用 str.format()
  • 命令行参数入门用 sys.argv,进阶用 argparse。

延伸阅读:

下一篇 条件语句 if/elif/else 我们要开始让程序做”选择”了。

字符串基础与常用方法

字符串是编程中出现频率最高的数据类型之一——处理用户输入、读写文件、拼接 URL、格式化日志、拆解 CSV,几乎每个 Python 脚本都少不了它。前面 变量与基本数据类型 已经打过照面,这一篇要把 Python 字符串从零到能干活讲完整:创建、索引、切片、常用方法(超过 30 个)、编码与字节、以及最容易踩的那几个坑。

一、字符串的创建

Python 字符串支持四种界定方式:

1
2
3
4
5
6
7
8
9
10
11
s1 = 'hello'          # 单引号
s2 = "hello" # 双引号,与单引号完全等价
s3 = '''多行
字符串''' # 三引号,可跨行
s4 = """多行
字符串"""

# 里面含有引号
q1 = "He said 'hi'"
q2 = 'It\'s ok' # 用反斜杠转义
q3 = "It's ok" # 或者混用引号

惯例:普通字符串用双引号,需要输出双引号的字符串用单引号,跨多行的用三引号。别纠结,一致就行。

转义字符

1
2
3
4
5
6
print("换行\n再来一行")
print("\t缩进") # 制表符
print("反斜杠 \\")
print("Unicode: 中文") # 中文
print("原生字符串 r'C:\Users\name'") # 会转义 \U
print(r"原生字符串 r'C:\Users\name'") # 不转义,Windows 路径必备

Windows 路径写字符串时务必加 r,或者用双反斜杠、或者用正斜杠:

1
2
3
path = r"C:\Users\name\file.txt"       # 推荐
path = "C:\\Users\\name\\file.txt" # 也行
path = "C:/Users/name/file.txt" # 也行

二、字符串是不可变的

Python 字符串不可变(immutable)——一旦创建,不能修改任何一个字符:

1
2
s = "hello"
s[0] = "H" # TypeError!

要”修改”字符串,你只能创造一个新字符串:

1
2
3
s = "H" + s[1:]        # "Hello"
s = s.replace("h", "H")
s = s.upper()

这个特性的好处:字符串可以做字典的 key、可以放进 set、可以在多线程里安全共享。

三、索引与切片

字符串本质上是”字符的序列”,支持索引和切片(与列表一致):

1
2
3
4
5
6
7
8
9
10
s = "Python"
print(s[0]) # P,从 0 开始
print(s[-1]) # n,负数索引从右往左
print(len(s)) # 6

print(s[0:2]) # Py,切片 [start:stop),不含 stop
print(s[2:]) # thon,省略 stop 到结尾
print(s[:3]) # Pyt,省略 start 从头开始
print(s[::-1]) # nohtyP,反转字符串
print(s[::2]) # Pto,步长为 2

切片永远返回新字符串,不会修改原字符串。切片深入讲解见 序列切片深入解析

四、遍历字符串

字符串可迭代,直接 for 循环即可:

1
2
3
4
5
6
7
8
for ch in "咖飞":
print(ch)
# 咖
# 飞

# 需要索引时:
for i, ch in enumerate("Python"):
print(i, ch)

五、常用方法(按功能分组)

1. 大小写

1
2
3
4
5
6
7
s = "Hello, Python"

print(s.upper()) # HELLO, PYTHON
print(s.lower()) # hello, python
print(s.title()) # Hello, Python 每个词首字母大写
print(s.capitalize()) # Hello, python 首字母大写,其余小写
print(s.swapcase()) # hELLO, pYTHON 大小写互换

2. 检索

1
2
3
4
5
6
7
8
9
s = "hello world"

print(s.find("o")) # 4 第一次出现的位置,找不到返回 -1
print(s.rfind("o")) # 7 从右往左找
print(s.index("o")) # 4 同 find,但找不到抛 ValueError
print(s.count("l")) # 3 出现次数

print(s.startswith("he")) # True
print(s.endswith("ld")) # True

3. 判断

1
2
3
4
5
6
7
print("abc123".isalnum())    # True,全是字母数字
print("abc".isalpha()) # True,全是字母
print("123".isdigit()) # True,全是数字
print(" ".isspace()) # True,全是空白
print("ABC".isupper()) # True
print("abc".islower()) # True
print("Hello World".istitle()) # True

4. 修剪

1
2
3
4
5
6
7
8
9
10
11
12
s = "   hello   "
print(repr(s.strip())) # 'hello' 去两端空白
print(repr(s.lstrip())) # 'hello '
print(repr(s.rstrip())) # ' hello'

# 也可以去除指定字符
print("__abc__".strip("_")) # abc
print("www.baidu.com".lstrip("w.")) # baidu.com

# Python 3.9+ 有更精确的 removeprefix / removesuffix
print("test_module.py".removesuffix(".py")) # test_module
print("Mr. Wang".removeprefix("Mr. ")) # Wang

注意 strip("abc") 是去除”任意 a/b/c 字符”,不是去除子串 "abc"。用 removeprefix/removesuffix 才是去除子串。

5. 替换

1
2
3
s = "hello world hello"
print(s.replace("hello", "hi")) # hi world hi
print(s.replace("hello", "hi", 1)) # hi world hello,最多替换 1 次

6. 拆分与拼接

1
2
3
4
5
6
7
8
9
10
# 拆分
"a,b,c".split(",") # ['a', 'b', 'c']
"a b c".split() # ['a', 'b', 'c'] 不带参数按连续空白拆
"a,b,c".rsplit(",", 1) # ['a,b', 'c'] 从右拆一次
"line1\nline2\nline3".splitlines() # ['line1', 'line2', 'line3']

# 拼接:str.join(iterable) 是标准做法
",".join(["a", "b", "c"]) # 'a,b,c'
"\n".join(["行1", "行2"]) # '行1\n行2'
"".join(["a", "b", "c"]) # 'abc'

为什么用 join 不用 + 循环里 += 拼接字符串每次都会创建新对象,是 O(n²);而 "".join(list) 是 O(n)。写循环拼接大量字符串时永远用 join

1
2
3
4
5
6
7
# ❌ 慢,代码丑
result = ""
for word in words:
result += word + " "

# ✅ 快,代码美
result = " ".join(words)

7. 填充与对齐

1
2
3
4
5
"5".zfill(4)                # '0005'  左侧补 0

"abc".ljust(10, "-") # 'abc-------'
"abc".rjust(10, "-") # '-------abc'
"abc".center(10, "-") # '---abc----'

在打印表格、生成对齐输出时很有用。

8. 编码相关

1
2
3
s = "咖飞"
b = s.encode("utf-8") # b'\xe5\x92\x96\xe9\xa3\x9e' bytes 对象
s2 = b.decode("utf-8") # '咖飞'
  • str 是 Unicode 字符串(人读的);
  • bytes 是字节序列(机器传的);
  • encode 把 str → bytes,decode 把 bytes → str;
  • 中文一般用 UTF-8,遇到旧文件可能用 GBK。

六、格式化字符串(简版)

字符串格式化有三种主流方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name = "咖飞"
age = 3

# 1. f-string(Python 3.6+,最推荐)
print(f"我叫 {name}{age} 岁")
print(f"两位小数:{3.14159:.2f}") # 3.14
print(f"补 4 位:{7:04d}") # 0007

# 2. str.format
print("我叫 {},{} 岁".format(name, age))
print("我叫 {n},{a} 岁".format(n=name, a=age))

# 3. % 老式,兼容 C printf
print("我叫 %s,%d 岁" % (name, age))

新代码永远用 f-string。format 和 % 只在维护老代码时才会遇到。

字符串格式化的完整讲解见 字符串格式化进阶

七、字符串常量

Python 标准库 string 里有一些方便的常量:

1
2
3
4
5
6
7
import string

print(string.ascii_letters) # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
print(string.ascii_lowercase) # abcdefghijklmnopqrstuvwxyz
print(string.digits) # 0123456789
print(string.punctuation) # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
print(string.whitespace) # 空白字符(含 \n \t)

生成随机密码时特别好用:

1
2
3
4
5
6
7
8
import random
import string

def gen_password(length: int = 12) -> str:
chars = string.ascii_letters + string.digits + string.punctuation
return "".join(random.choices(chars, k=length))

print(gen_password())

八、字符串与列表的互转

1
2
3
4
5
6
7
8
s = "咖飞"
lst = list(s) # ['咖', '飞']
back = "".join(lst) # '咖飞'

# 单词切分
sentence = "I love Python"
words = sentence.split() # ['I', 'love', 'Python']
back = " ".join(words) # 'I love Python'

九、一个完整例子:日志脱敏

我们把这一篇的知识揉到一起,写一个”手机号脱敏”工具:

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
34
35
36
37
38
39
40
41
# mask.py
"""把日志里的手机号和邮箱替换成脱敏形式。"""

def mask_phone(phone: str) -> str:
"""138****1234 形式。"""
if len(phone) != 11 or not phone.isdigit():
return phone
return phone[:3] + "*" * 4 + phone[-4:]


def mask_email(email: str) -> str:
"""把 @ 前只保留首尾两个字符。"""
if "@" not in email:
return email
name, domain = email.split("@", 1)
if len(name) <= 2:
masked = name
else:
masked = name[0] + "*" * (len(name) - 2) + name[-1]
return f"{masked}@{domain}"


def mask_line(line: str) -> str:
"""对一行日志同时脱敏(简化版:这里只演示 str 方法,实际生产建议用 re)。"""
words = line.split()
return " ".join(
mask_email(w) if "@" in w else
mask_phone(w) if len(w) == 11 and w.isdigit() else
w
for w in words
)


if __name__ == "__main__":
logs = [
"user 13812345678 login ok",
"notify kafei@example.com 已发送",
"顺序无关 admin 18899990000 alice@a.cn",
]
for line in logs:
print(mask_line(line))

输出:

1
2
3
user 138****5678 login ok
notify k****i@example.com 已发送
顺序无关 admin 188****0000 a****e@a.cn

真实生产环境的日志脱敏建议用 正则表达式 re 模块,本例只是演示字符串方法组合的威力。

十、常见陷阱

  1. strip("abc") 不是去掉子串,而是去掉两端属于 a/b/c 的任意字符。去子串用 removeprefix/removesuffix
  2. in 判断子串包含,别写成 s.find(x) != -1if "py" in s: 更 Pythonic。
  3. 不要在循环里 += 拼字符串,用 list.append + "".join
  4. isdigit() 对负号和小数点返回 False"-3".isdigit() 是 False,判断”是否是数字”最好写 try/except int()。
  5. Windows 路径中的反斜杠"C:\new\test.py" 会把 \n 解析成换行。用 raw string r"..." 最省心。
  6. 多行 f-string 里的花括号:想输出真正的 {,要写 {{`;输出 `}` 写 `}}
  7. split() 无参数与有参数不同:无参数按任意连续空白切且忽略首尾,split(" ") 是严格按单空格切且保留空串。
1
2
"  a  b  ".split()       # ['a', 'b']
" a b ".split(" ") # ['', '', 'a', '', 'b', '', '']

十一、小结与延伸阅读

  • 字符串不可变,索引切片都返回新字符串;
  • 用 f-string 做格式化;
  • join 不用 += 做拼接;
  • 熟悉 strip / split / replace / find / startswith / endswith 这几个高频方法;
  • 中文场景注意 encode/decode,默认用 UTF-8;
  • Windows 路径记得 r"..."
  • 需要更强查找/替换能力时上正则。

延伸阅读:

下一篇 输入输出与格式化字符串 我们要让程序真正开口和用户对话。

运算符与表达式

变量装着数据,运算符让数据”流动起来”。上一篇 变量与基本数据类型 我们介绍了 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 中用得最多的类型之一。

变量与基本数据类型

变量是编程的第一块砖。写程序其实就是”用变量装数据,让数据流动”的过程。上一篇 第一个 Python 程序 里我们已经悄悄用过变量了(name = "咖飞")。这一篇我们要正式讲清楚:Python 的变量到底是什么?有哪些基本数据类型?为什么 Python 是”动态类型”?以及一堆新手容易翻车的细节。学完这一篇,你写的代码就能装东西了。

一、变量到底是什么

在 C 语言里,变量是”一块贴了标签的内存”;在 Python 里,思维要反过来——变量是贴在对象上的标签

1
name = "咖飞"

这一行代码干了三件事:

  1. 内存里创建了一个字符串对象 "咖飞"
  2. 定义了一个叫 name 的名字;
  3. name 指向那个对象。

所以 Python 的赋值不是”把值放进变量”,而是”把标签贴到对象上”。这个模型很重要,理解它可以省掉后面 80% 的困惑。

来看一段能证明这个观点的代码:

1
2
3
4
a = [1, 2, 3]
b = a # b 和 a 贴在同一个列表对象上
b.append(4)
print(a) # 输出 [1, 2, 3, 4],因为 a 和 b 指的是同一个对象

如果按”变量是盒子”的模型,你会觉得 b = a 是复制一份,改 b 不影响 a。但按”变量是标签”的模型,就一目了然:两个标签贴在同一件行李上,动一个另一个当然跟着变。

二、变量命名规则与惯例

Python 变量命名规则:

  • 由字母、数字、下划线组成;
  • 不能以数字开头;
  • 不能与关键字冲突(如 ifforclassTrue 等);
  • 区分大小写ageAge 是两个不同的变量;
  • 支持 Unicode,名字 = "咖飞" 也合法,但不推荐。

命名风格惯例(PEP 8):

  • 普通变量、函数:snake_case(小写下划线),如 user_name
  • 常量:UPPER_CASE,如 MAX_RETRY
  • 类名:PascalCase,如 UserAccount
  • “私有”变量:前缀单下划线,如 _internal
  • 避免用单字符(除了 ij 之类循环变量);
  • 变量名要有意义——d = 86400 不如 seconds_per_day = 86400
1
2
3
4
5
6
7
8
9
# 好
user_age = 18
MAX_RETRY = 3
class UserAccount: ...

# 不好
a = 18
x1 = 3
class user_account: ...

三、基本数据类型总览

Python 内置基本类型有 6 大类

类型 关键字 举例 说明
整数 int 42, -7, 0, 10_000 任意精度整数
浮点数 float 3.14, 1e5, -0.5 双精度浮点
字符串 str "hi", '咖飞', """...""" 不可变的字符序列
布尔 bool True, False int 的子类
空值 None None 表示”没有值”
复数 complex 1+2j 一般用不到,本文只提一下

先只讲前 5 个,复数遇到再说。

四、int:整数

Python 3 的整数是”任意精度”,不用担心溢出:

1
2
3
big = 2 ** 128
print(big) # 340282366920938463463374607431768211456
print(type(big)) # <class 'int'>

支持不同进制字面量:

1
2
3
4
5
dec = 255         # 十进制
hex_num = 0xFF # 十六进制
oct_num = 0o377 # 八进制
bin_num = 0b1111_1111 # 二进制
print(dec, hex_num, oct_num, bin_num) # 全部输出 255

Python 3.6+ 允许下划线做视觉分隔:

1
2
population = 1_400_000_000   # 十四亿
print(population) # 1400000000,下划线只是给人看的

五、float:浮点数

浮点数用双精度 IEEE 754,注意精度问题:

1
2
print(0.1 + 0.2)             # 0.30000000000000004,不是 0.3!
print(0.1 + 0.2 == 0.3) # False

这不是 Python 的 bug,而是所有基于 IEEE 754 的语言的通病。结论:不要用 == 比较浮点数。要用容差:

1
2
import math
math.isclose(0.1 + 0.2, 0.3) # True

或者对财务/金额场景,用 decimal 模块:

1
2
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # 0.3

科学计数法也可以:

1
2
c = 3e8        # 3 * 10^8,光速
print(c) # 300000000.0

六、str:字符串

字符串是 Python 里最常用的类型之一:

1
2
3
4
5
6
7
8
9
s1 = "双引号"
s2 = '单引号'
s3 = """三引号
可以跨行
"""
s4 = '''三个单引号,也可以'''

print(len(s1)) # 3,字符个数
print(s3.strip()) # 去掉首尾空白

字符串支持”加法”(拼接)和”乘法”(重复):

1
2
print("ab" + "cd")      # abcd
print("=" * 20) # ====================

字符串是不可变的——你不能修改一个已有字符串的某一位:

1
2
3
s = "hello"
s[0] = "H" # TypeError: 'str' object does not support item assignment
s = "H" + s[1:] # 正确做法:造一个新字符串

关于字符串还有更多内容,我们会在 字符串基础与常用方法 单独讲。

七、bool:布尔

TrueFalse,注意首字母大写。它们其实是 int 的子类:

1
2
3
print(True + True)     # 2,True 相当于 1
print(False * 100) # 0
print(isinstance(True, int)) # True

Python 里的真值判断很宽松,以下都被视为”假”:

  • False
  • None
  • 00.00j
  • 空字符串 ""、空列表 []、空字典 {}、空集合 set()、空元组 ()

其他一切都是”真”。所以:

1
2
3
4
5
6
if items:               # items 非空就为真,简洁又 Pythonic
print("非空")

# 不推荐这样写:
if len(items) > 0: # 冗长
print("非空")

八、None:什么都不是

None 表示”没有值”,函数没有 return 时默认返回 None

1
2
3
4
5
6
def do_nothing():
pass

result = do_nothing()
print(result) # None
print(result is None) # True

判断 Noneis,不要用 ==is 判断”是不是同一个对象”,None 全局只有一个;== 是”值相等”,可能被自定义类重写而产生意外行为。

九、类型转换

Python 支持显式类型转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 数字与字符串
s = "42"
n = int(s) # 42
f = float(s) # 42.0
str_n = str(3.14) # "3.14"

# 布尔与数字
print(int(True)) # 1
print(bool(0)) # False
print(bool("")) # False
print(bool("False")) # True,因为字符串非空即真!

# 二进制、十六进制字符串
print(int("ff", 16)) # 255
print(int("1010", 2)) # 10
print(hex(255)) # '0xff'
print(bin(10)) # '0b1010'

陷阱int("3.14") 会报错,因为 "3.14" 不是合法整数字符串。要先转成 float 再转 int:int(float("3.14")) 得到 3

十、typeisinstance

想知道一个变量是什么类型:

1
2
3
4
x = 42
print(type(x)) # <class 'int'>
print(type(x) == int) # True
print(isinstance(x, int)) # True,推荐用这个

isinstancetype == ... 更好,因为它支持继承关系(比如 isinstance(True, int)True)。

十一、动态类型 vs 静态类型

Python 是动态类型语言:

1
2
3
x = 1          # 现在 x 是 int
x = "hello" # 现在 x 是 str
x = [1, 2, 3] # 现在 x 是 list

这三行代码都合法。同一个变量名可以贴到不同类型的对象上,非常灵活,但也带来风险:代码一多,你不知道某个变量此刻到底是什么类型。因此 Python 3.5+ 引入了 类型注解

1
2
name: str = "咖飞"
age: int = 3

注解不改变运行时行为(Python 不会真的检查类型),只是给 IDE 和静态检查工具 mypy 看的。养成写类型注解的习惯,能省下大量调试时间。

十二、常见陷阱

陷阱 1:可变默认参数(本篇预告)

1
2
3
def add_item(item, items=[]):    # 危险!
items.append(item)
return items

items=[] 只在函数定义时执行一次,多次调用会共享同一个列表。这个坑我们在 参数详解 里会详细讲。

陷阱 2:is== 分不清

1
2
3
4
5
6
7
a = 256
b = 256
print(a is b) # True,因为小整数被 CPython 缓存了

a = 257
b = 257
print(a is b) # 可能是 False!CPython 只缓存 -5 到 256

结论:判断值相等永远用 ==,判断”是不是同一个对象”才用 is(一般只用于 is None)。

陷阱 3:数字前导 0

在 Python 3 里 x = 007 会报语法错误,因为过去八进制字面量是 0 开头,容易和十进制混淆,现在必须写 0o7

陷阱 4:整数除法

1
2
3
print(5 / 2)     # 2.5(float 结果)
print(5 // 2) # 2(整除)
print(5 % 2) # 1(取余)

Python 2 的 / 是整除,Python 3 改成了浮点除法。所有网上 Python 2 时代的代码到 3 里都要小心这一条。

陷阱 5:字符串数字直接相加

1
2
3
n = input("年龄:")
print(n + 1) # TypeError!n 是字符串
print(int(n) + 1) # 正确

input() 永远返回字符串。

十三、小结与延伸阅读

  • 变量是”贴在对象上的标签”,不是”存放值的盒子”;
  • 命名遵循 PEP 8:snake_case、UPPER_CASE、PascalCase;
  • 6 大基本类型:int、float、str、bool、None、complex;
  • Python 是动态类型,但配合 type hints 可以获得静态检查;
  • 浮点数不精确,0.1 + 0.2 != 0.3
  • is 判断对象身份,== 判断值相等,除非判 None 否则用 ==
  • input() 返回字符串,需要手动转类型。

延伸阅读:

下一篇 运算符与表达式 我们要用这些变量做点计算了。

第一个 Python 程序:Hello World 与运行方式

安装完 Python,就迫不及待想写代码了。上一篇 环境搭建:安装 Python 与配置 IDE 我们已经装好了环境。这一篇要正式跑起来第一段代码 “Hello World”,同时把 Python 的四种主流运行方式一次讲清楚:交互式 REPL、脚本文件、模块方式、Jupyter Notebook。理解这四种方式的适用场景,你就理解了 Python 程序是”怎么活起来”的。

一、Hello World 的四种写法

先来一个最简单的:

1
print("Hello, World!")

就这一行。放到不同环境里跑,你会得到不同的体验。下面依次演示。

二、方式一:交互式解释器(REPL)

REPL = Read Eval Print Loop = 读入 → 求值 → 打印 → 循环。它是一个”命令行 Python”,非常适合快速试代码。

进入 REPL

打开终端:

1
2
python              # Windows
python3 # macOS/Linux

看到类似这样的提示符:

1
2
3
Python 3.13.0 (main, Oct  7 2025, 12:00:00)
Type "help", "copyright", "credits" or "license" for more information.
>>>

>>> 就是 Python 在等你输入。试试:

1
2
3
4
5
6
7
>>> print("Hello, World!")
Hello, World!
>>> 1 + 1
2
>>> name = "咖飞"
>>> f"你好,{name}"
'你好,咖飞'

注意 REPL 有一个便利之处:表达式的值会自动打印,不需要 print

1
2
3
4
>>> 3 * 4
12
>>> "abc".upper()
'ABC'

但这个便利只在 REPL 里成立,写进 .py 脚本文件就不打印了。

退出 REPL

  • Windows:Ctrl + Z 回车,或输入 exit()
  • macOS/Linux:Ctrl + D,或输入 exit()

多行代码

REPL 也能写多行代码,遇到 : 会自动进入多行输入:

1
2
3
4
5
6
>>> for i in range(3):
... print(i)
...
0
1
2

按下 Enter 后跟着 ... 的行都是”多行输入”,最后再按一次 Enter(空行)结束。

REPL 适合什么场景?

  • 快速验证一个 API 怎么用;
  • 临时算个数、转个进制;
  • 学新语法时的”试练场”。

不适合:写完整程序、频繁调试、代码量超过 20 行——那种情况就需要脚本文件了。

三、方式二:脚本文件 .py

这是最常用的方式。把代码写进一个 .py 文件,用 Python 运行它。

新建文件

用你喜欢的 IDE(推荐 VS Code)新建 hello.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# hello.py
# 我的第一个 Python 脚本
# 上面的 # 开头的行是注释,Python 会忽略它们

print("Hello, World!")
print("我在学 Python,很开心。")

# 变量
name = "咖飞"
age = 3
print(f"我叫 {name},今年 {age} 岁。")

# 简单计算
result = (100 + 50) * 2
print(f"结果是 {result}")

运行

在文件所在目录打开终端:

1
2
python hello.py              # Windows
python3 hello.py # macOS/Linux

输出:

1
2
3
4
Hello, World!
我在学 Python,很开心。
我叫 咖飞,今年 3 岁。
结果是 300

关于文件路径

  • 推荐做法:cd 到脚本所在目录,然后 python hello.py
  • 也可以用绝对路径python D:\code\hello.py(Windows)或 python /home/gkbn/code/hello.py(Linux)。

关于中文

Python 3 源代码默认 UTF-8,你直接写中文没有问题。但请确保:

  • 编辑器用 UTF-8 保存文件(VS Code、PyCharm 默认就是);
  • 如果 Windows 命令行显示乱码,可执行 chcp 65001 切换到 UTF-8 编码。

四、方式三:作为模块运行 python -m

-m 参数把某个模块当作脚本来运行,非常有用:

1
2
3
4
python -m http.server 8000   # 一行命令启动一个静态文件服务器
python -m json.tool < a.json # 格式化 JSON 文件
python -m venv .venv # 创建虚拟环境
python -m pip install requests # 显式指定当前 python 的 pip

为什么要写 python -m pip 而不是直接 pip 因为电脑上可能有多个 Python,直接 pip 有时候会把包装到另一个 Python 里。用 python -m pip 强制它跟当前 python 是一伙的。

五、方式四:Jupyter Notebook(可选)

Notebook 是一种”网页上的交互式 Python 环境”,适合做数据分析、机器学习、教学演示。它的特点是把代码分成一个个”单元格”(cell),每个 cell 单独运行、单独看结果。

安装并启动

1
2
pip install jupyterlab
jupyter lab

浏览器会自动打开 http://localhost:8888,新建 Notebook,就可以边写代码边看结果。

对于纯教学场景,Notebook 非常直观;但如果你要写一个完整的应用(比如一个 Web 服务),还是 .py 文件更合适。本教程后续所有代码都以 .py 脚本形式给出

六、shebang:给脚本”加双翅膀”

在 macOS/Linux 上,你可以给脚本第一行加个 shebang:

1
2
3
#!/usr/bin/env python3
# hello.py
print("Hello, World!")

然后给它执行权限:

1
2
chmod +x hello.py
./hello.py # 直接运行,不再需要 python 前缀

Windows 用户不需要 shebang,因为它是 Unix 世界的机制。

七、Python 到底是”编译”还是”解释”?

严格来说,Python 是”编译成字节码然后由解释器执行“。当你运行 python hello.py,实际过程是:

  1. 词法/语法分析:解析 hello.py
  2. 编译为字节码.pyc 文件,缓存在 __pycache__ 目录);
  3. 虚拟机执行字节码

所以你在项目里看到的 __pycache__ 文件夹,就是这个字节码缓存。它可以放心删除,Python 会在下次运行时重新生成。

八、常见错误与排查

错误 1:SyntaxError: invalid syntax

新手常犯的是中英文标点混用。比如:

1
2
3
4
5
# 错的:括号是中文括号
print"Hello"

# 对的:括号是英文括号
print("Hello")

复制自 Word 或聊天软件里的代码尤其容易出这个问题。

错误 2:IndentationError: unexpected indent

Python 强制缩进,一行行的开头必须对齐。不要混用 Tab 和空格。推荐 IDE 里设置”Tab 输入 4 个空格”。

错误 3:NameError: name 'xxx' is not defined

变量还没赋值就用了:

1
2
print(name)   # NameError,因为 name 从来没定义过
name = "咖飞"

Python 是顺序执行的,用之前必须定义。

错误 4:FileNotFoundError / “找不到文件”

python hello.py 报文件找不到,通常是因为你在错误的目录下运行。用 pwd(macOS/Linux)或 cd(Windows)确认当前所在目录。

错误 5:REPL 里退出不了

Windows 终端里 Ctrl+D 不管用,要用 Ctrl+Z 然后回车,或者输入 exit()

九、一个稍复杂的完整示例

我们把这一篇的知识做一个整合,写一个”打招呼小工具”:

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
# greet.py
# 一个简易的打招呼工具

import datetime # 内置模块,处理日期时间

def get_greeting():
"""根据当前时间返回合适的问候语。"""
hour = datetime.datetime.now().hour
if hour < 6:
return "夜猫子"
elif hour < 12:
return "早上好"
elif hour < 18:
return "下午好"
else:
return "晚上好"


def main():
name = input("请告诉我你的名字:").strip()
if not name:
name = "陌生人"
greeting = get_greeting()
print(f"{greeting}{name}!")
print(f"当前时间:{datetime.datetime.now():%Y-%m-%d %H:%M:%S}")


if __name__ == "__main__":
main()

保存后运行:

1
python greet.py

现在你的第一段 Python 程序已经具备”读取输入 → 判断时段 → 输出问候”的完整逻辑了。虽然还有很多语法没讲(函数、if、__name__ 等),但没关系——它们后面几篇都会展开。

十、小结与延伸阅读

  • REPL 适合快速试代码,.py 脚本适合写完整程序;
  • python -m 是一个被低估的实用技巧;
  • Jupyter Notebook 适合数据分析和教学;
  • Python 实际是”编译成字节码再解释执行”;
  • 新手最容易踩的坑:中英文标点、缩进不一致、变量未定义;
  • 从这一篇开始,请把每个示例都亲手敲一遍。

延伸阅读:

下一篇 变量与基本数据类型 我们会讲 Python 里最基本的”数据积木”。

环境搭建:安装 Python 与配置 IDE

上一篇 Python 简介与发展历史 我们讲清楚了 Python 是什么。这一篇要开始动手了。很多人学编程折在了第一步——装环境。装了三小时 Python,还没写一行代码,就已经想放弃了。本文的目标就是把你从这种痛苦里救出来:Windows、macOS、Linux 三平台一次讲清楚,同时给出一个 2025 年主流的 IDE 选型建议。读完你应该能在自己的电脑上顺利执行 python --version 并进入 IDE 敲下第一段代码。

一、选择 Python 版本

打开 https://www.python.org/downloads/ 你会看到很多版本。新手一律选择最新的稳定版(截至 2025 年 9 月,这个版本是 Python 3.13.x)。如果你必须与旧项目兼容,可以选 3.10 或 3.11,本系列教程 100% 兼容 3.10+。

不要装 Python 2。Python 2 已于 2020 年 EOL(停止维护),任何现代教程或项目都不再支持它。

有些教材推荐 Anaconda,那是”Python + 一堆数据科学库”的整体发行版。如果你目标是数据分析/AI,Anaconda 确实方便;但作为通用学习,Anaconda 太重,还常与官方 Python 冲突,本教程推荐使用官方 Python + pip + venv的最小组合。

二、Windows 下安装 Python

步骤 1:下载安装包

https://www.python.org/downloads/windows/ 下载 “Windows installer (64-bit)”。

步骤 2:勾选 Add python.exe to PATH

安装向导第一屏底部有一个小勾选框 “Add python.exe to PATH”,必须勾上!这个勾没勾,是新手 90% 环境问题的根源。勾上之后,你在任意目录打开命令行都能直接调用 python

然后点 “Install Now” 一路默认即可。

步骤 3:验证

按下 Win + R,输入 cmd 回车,在命令行中输入:

1
python --version

如果显示 Python 3.13.x(或你安装的版本号),恭喜,装成功了。

如果出现 “python 不是内部或外部命令”,说明 PATH 没添加。解决方案:卸载 → 重装 → 记得勾选 Add to PATH。或者手动到 “系统属性 → 环境变量 → Path” 里添加 Python 安装目录(例如 C:\Users\你的用户名\AppData\Local\Programs\Python\Python313\)。

Windows 特别提示:py 启动器

Windows 版 Python 自带一个 py 启动器,可以同时管理多个版本:

1
2
3
py -3.10     # 启动 Python 3.10
py -3.13 # 启动 Python 3.13
py # 启动默认版本

三、macOS 下安装 Python

macOS 自带一个 python3,但版本旧且不能随便升级。推荐两种方式:

方式 1:官网 pkg 安装

https://www.python.org/downloads/macos/ 下载 macOS installer,双击 pkg 一路点下一步即可。

方式 2:Homebrew(推荐)

如果你已经安装了 Homebrew(一个 macOS 包管理器):

1
brew install python@3.13

安装完成后:

1
python3 --version

在 macOS 上通常需要输入 python3 而不是 python(因为系统自带一个 Python 2 时代的 python 命令),这个习惯请记住。

四、Linux 下安装 Python

主流发行版都自带 Python 3,但版本可能不够新。以 Ubuntu 22.04+ 为例:

1
2
3
sudo apt update
sudo apt install python3 python3-pip python3-venv
python3 --version

如果需要更新的版本,可以用 deadsnakes PPA:

1
2
3
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.13 python3.13-venv

CentOS/RHEL 系用 dnfyumsudo dnf install python3.13

五、pip:Python 的包管家

Python 3.4+ 自带 pip,安装完 Python 后直接可用:

1
2
pip --version              # Windows
pip3 --version # macOS/Linux

pip 是安装第三方库的工具。举个例子,我们后面要用的 requests 库:

1
pip install requests

由于 pip 默认从国外服务器下载,国内用户常常慢到怀疑人生。可以换成清华源:

1
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

设置一次全局生效。

六、IDE 选型:2025 年的推荐

写 Python 用什么工具?下面按学习阶段给出建议:

1. VS Code(推荐 · 全阶段可用)

  • 免费、跨平台、启动快、插件丰富;
  • 装两个官方插件:Python + Pylance(微软出品,提供强大的类型检查和自动补全);
  • 内置 Jupyter Notebook 支持,可用于数据分析;
  • 是本教程默认推荐的 IDE。

2. PyCharm(推荐 · 面向工程)

  • JetBrains 出品,功能最全的 Python IDE;
  • Community 版免费够用,Professional 版对 Django/Flask/科学计算支持更强;
  • 内存占用较大,但重构、调试、导航体验极佳;
  • 适合写中大型项目。

3. Jupyter Notebook / JupyterLab

  • 数据分析、机器学习必备;
  • 交互式笔记本,适合边写代码边看结果;
  • 不适合写完整的应用。

4. IDLE(不推荐)

  • Python 自带的最简 IDE;
  • 只适合看官方教程时快速试一段代码;
  • 不建议长期使用。

5. Cursor / Windsurf 等 AI IDE

  • 基于 VS Code 二次开发,内置 AI 补全;
  • 新手不推荐——你会习惯让 AI 帮你写代码,导致基础不扎实;
  • 学完前 30 篇再上手,会真香。

七、配置 VS Code 写第一段 Python

假设你选了 VS Code,我们完整走一遍配置:

步骤 1:安装 VS Code

https://code.visualstudio.com/ 下载安装,一路默认。

步骤 2:安装 Python 插件

  • 打开 VS Code → 左侧插件市场(Ctrl+Shift+X);
  • 搜索 “Python”,安装 Microsoft 出品的那个(Pylance 会一起装上)。

步骤 3:新建项目并写代码

  • 在电脑上任意位置新建一个文件夹 python-learn
  • VS Code → File → Open Folder → 选中它;
  • 在这个文件夹里新建 hello.py
1
2
3
4
5
# hello.py
# 我的第一段 Python 代码

name = input("请输入你的名字:")
print(f"你好,{name}!欢迎来到 Python 世界。")

步骤 4:选择 Python 解释器

Ctrl+Shift+P 打开命令面板,输入 “Python: Select Interpreter”,选择你刚才装好的 Python 版本。这是 每个 Python 项目都要做的事,否则代码补全和运行会出问题。

步骤 5:运行

  • 方法一:VS Code 右上角有个绿色三角形运行按钮,点它;
  • 方法二:打开集成终端 (`Ctrl+\),输入 python hello.py

看到 “请输入你的名字:” 说明一切就绪。

八、创建虚拟环境(建议养成的好习惯)

尽管我们要在第 25 篇 pip 与虚拟环境 venv 中详细讲,但强烈建议从第一天就养成用虚拟环境的习惯。它的意义是:每个项目一个独立的 Python 依赖沙箱,互不干扰

在项目根目录执行:

1
2
3
4
5
6
7
# Windows
python -m venv .venv
.venv\Scripts\activate

# macOS/Linux
python3 -m venv .venv
source .venv/bin/activate

激活后你会看到命令行前多了 (.venv),此时用 pip install xxx 装的库只属于这个项目。退出虚拟环境用 deactivate

在 VS Code 中,Ctrl+Shift+P → “Python: Select Interpreter” 选中 .venv 里的解释器,之后所有终端和运行都会走虚拟环境。

九、常见问题排查

Q1:终端 python 命令能用,但 VS Code 报”未找到 Python”。
A:在 VS Code 里手动指定解释器路径(Select Interpreter → Enter interpreter path)。

Q2:pip install 报 SSL 错误。
A:升级 pip:python -m pip install --upgrade pip;或改用清华/阿里镜像源。

Q3:装了两个 Python 版本,运行的时候搞不清用了哪个。
A:python -c "import sys; print(sys.executable)" 可以打印当前解释器的绝对路径。

Q4:Windows 下运行 python 弹出应用商店。
A:这是 Windows 的”Python 别名快捷方式”。到 “设置 → 应用 → 应用别名”里关掉,或者装好官方 Python 后即可覆盖。

Q5:中文乱码。
A:现代 Python 3 默认 UTF-8,但个别 Windows 终端还用 GBK。可以在文件顶部加上 # -*- coding: utf-8 -*-(其实 3.x 不需要);或者设置终端编码为 UTF-8:chcp 65001

十、常见陷阱

  1. 忘记勾选 Add to PATH —— 装完 Python 用不了,是最常见 Windows 陷阱;
  2. 系统里存在多个 Python —— macOS/Linux 有一个系统自带、一个 Homebrew、一个 pyenv,pip install 可能装到”错的那个”,结果代码里 import 失败;
  3. 在系统 Python 里 pip install 一堆库 —— 项目多了就打架,因此建议虚拟环境;
  4. IDE 里没选对解释器 —— 代码补全一片红,其实语法都是对的;
  5. 用 root/sudo 安装 pip 包 —— 权限混乱,久了必炸,永远不要 sudo pip install

十一、小结与延伸阅读

  • 官方 Python + pip + venv 是最纯净、最不容易出问题的组合;
  • Windows 装完记得勾选 Add to PATH;
  • macOS/Linux 上一般叫 python3,而不是 python
  • VS Code + Python 插件是新手最佳选择;
  • 从第一天起就用虚拟环境;
  • 遇到问题 90% 是解释器选错或者 PATH 没配好。

延伸阅读:

下一篇 第一个 Python 程序:Hello World 与运行方式 我们会开始真正的编程,教你用几种不同的方式运行 Python 代码。

Python 简介与发展历史

学习一门编程语言,很多人上来就打开教程写 print("Hello World")。但如果你连这门语言从哪里来、为什么长成今天这个样子都不清楚,那么当你后面遇到「为什么 Python 的除法要用两个斜杠?」「为什么缩进就能表示代码块?」这类问题时,就只能死记硬背。作为整个 Python 学习路线的第一篇,本文不会写任何一行 Python 代码,而是带你回到 1989 年的圣诞节,看看 Python 是怎么一步步长成今天全世界最流行的编程语言之一的。搞清楚它的血统与设计哲学,后面 49 篇教程你才能真正读进去。

一、Python 是什么

Python 是一门解释型、面向对象、动态类型、跨平台、通用的高级编程语言。这一串定语初学者不必立刻理解,但可以先建立以下印象:

  • 解释型:不像 C/C++ 那样需要「编译成 exe 再运行」,Python 代码写完可以直接运行,边翻译边执行,非常方便调试;
  • 动态类型:不需要提前声明变量类型,x = 1 之后再写 x = "hello" 也不会报错;
  • 面向对象:一切皆对象,包括整数、字符串、函数、甚至类本身;
  • 胶水语言:擅长把 C/C++/Rust 写的高性能库粘起来使用,这让它在科学计算和 AI 领域独占鳌头;
  • 通用:从爬虫、Web、数据分析、机器学习、自动化运维、量化交易到嵌入式脚本,几乎无所不在。

一句话总结:Python 是一门让你”少写代码就能干成大事”的语言

二、诞生:圣诞节的礼物

1989 年 12 月,荷兰程序员 Guido van Rossum(吉多·范罗苏姆)在阿姆斯特丹的 CWI 研究所度过了一个无聊的圣诞节。当时他正在参与一门叫 ABC 的教学语言的开发,ABC 有很多优秀的想法(比如强制缩进、内建高级数据类型),但商业上失败了。Guido 决定利用假期时间写一个”ABC 的继承者”,目标是:

  1. 简单易学,能取代 shell 脚本;
  2. 保留 ABC 的优点,同时可以调用 C 的库;
  3. 语法优雅,读起来像伪代码。

至于名字,Guido 是英国喜剧团体 Monty Python 的粉丝,就把这门语言命名为 Python(蟒蛇)。所以官方 Logo 里的两条蛇其实是”表面梗”,语言名真正的出处是喜剧节目《Monty Python’s Flying Circus》。这也解释了为什么很多 Python 教材(包括官方文档)里冷不丁就冒出一个 spam、eggs、Brian —— 这些都是 Monty Python 里的经典梗。

1991 年 2 月,Python 0.9.0 发布,Python 正式与世界见面。

三、三个大版本:从 1.x 到 3.x

Python 的历史可以清晰地分成三段:

1. Python 1.x(1994 – 2000)

  • 1994 年 1 月发布 1.0;
  • 引入了 lambda、map、filter、reduce 这些函数式特性;
  • 引入了模块系统。

此时的 Python 还只是一门小众脚本语言,用户主要集中在系统管理员和科研圈。

2. Python 2.x(2000 – 2020)

  • 2000 年 10 月发布 2.0,最有价值的变化是引入了垃圾回收Unicode 支持
  • 2.7 是 2.x 系列的最后一个版本,2010 年发布,2020 年 1 月 1 日正式停止维护(End of Life);
  • Python 2 时代出现了 Django、NumPy、SciPy、Matplotlib 等奠定 Python 生态的关键项目。

如果你今天在网上找教程,请一定要留意版本。凡是 print "hello"(没有括号)、raw_input()xrange 的代码,都是 Python 2 的代码,学了会走弯路。

3. Python 3.x(2008 – 至今)

Python 3.0 于 2008 年 12 月发布,最大的特点是故意不向下兼容。Guido 决定借这次大版本升级修掉一些历史遗留问题:

  • print 从语句变成函数:print("hi")
  • 所有字符串默认是 Unicode,字节串单独用 bytes
  • 整数除法 / 结果为浮点数,整除单独用 //
  • range 返回惰性对象而不是列表;
  • 移除了大量重复 API。

这次断裂式升级导致 Python 2 → 3 的迁移拖了整整十年。今天 3.x 已经迭代到 3.13,本教程默认使用 Python 3.10 及以上版本,这个版本引入了结构化 match 语句,同时性能也开始持续优化。

四、Python 之禅:读懂设计哲学

打开任意 Python 解释器输入 import this,会看到一段 Tim Peters 写的《Python 之禅》:

1
2
3
4
5
6
7
8
9
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
...
There should be one-- and preferably only one --obvious way to do it.

这不是随便写着玩的鸡汤,而是贯穿整个 Python 语言设计的价值观。理解它可以帮你解答很多”为什么”:

  • 为什么 Python 用缩进而不用大括号?—— 可读性优先
  • 为什么 Python 不支持”多种奇技淫巧写同一件事”?—— 应当有一种(最好只有一种)显而易见的做法
  • 为什么显式的 self 参数会被保留?—— 显式优于隐式

在后面的教程里,如果你觉得某种语法”啰嗦”,不妨回头再读一遍这段禅。

五、Python 的优缺点

任何语言都不是银弹,Python 也一样。

优点

  1. 语法接近自然语言,学习曲线平缓,是公认最适合作为第一门编程语言;
  2. 标准库+第三方库极其丰富,PyPI 上有超过 50 万个包,”人生苦短,我用 Python”;
  3. 胶水能力强,可以调用 C/C++/Rust/Fortran 写的模块,把 NumPy、PyTorch 这种性能怪兽当积木用;
  4. 社区庞大,遇到问题几乎都能在 Stack Overflow 找到答案;
  5. 跨平台,Windows/macOS/Linux 无缝切换。

缺点

  1. 性能相对较低:解释型 + GIL(全局解释器锁)让 CPU 密集型任务的多线程无法真正并行;不过通过 NumPy、Cython、多进程、asyncio 等手段可以绕开;
  2. 移动端支持弱:写 App 一般不会选 Python;
  3. 强依赖第三方:想做深度学习你得装 PyTorch,想做爬虫你得装 requests,”自己什么都能做”的印象背后是海量的依赖管理工作;
  4. 版本 / 环境管理复杂:这是新手第一大痛点,本教程会在 pip 与虚拟环境 venv 一篇单独讲解。

六、Python 在 2025 年的位置

根据 TIOBE 和 IEEE Spectrum 的排行榜,Python 已经连续多年稳居编程语言排行榜第一。它的主要应用领域包括:

  • 数据科学与 AI:Pandas、NumPy、Scikit-learn、PyTorch、TensorFlow;
  • Web 开发:Django、Flask、FastAPI;
  • 爬虫与自动化:requests、Scrapy、Selenium、Playwright;
  • DevOps:Ansible、Fabric、各种运维脚本;
  • 量化金融:backtrader、vnpy、AKShare;
  • 教育:从中学 STEAM 到大学计算机导论,Python 都是首选教学语言。

对新人来说,”学 Python 能干什么”的答案是:除了写操作系统内核和游戏引擎这种极致性能场景,几乎任何领域你都能用 Python 参与

七、如何用好本系列教程

本系列共 50 篇文章,围绕以下路线设计:

  1. 基础入门(1-10):语法、变量、条件、循环,能写出简单脚本;
  2. 数据结构(11-18):列表、字典、集合、推导式,是 Python 的”骨架”;
  3. 函数与模块(19-25):函数、闭包、装饰器入门、模块化开发;
  4. 面向对象(26-31):类、继承、魔术方法;
  5. 异常与文件(32-35):错误处理与 I/O;
  6. 中级进阶(36-45):迭代器、生成器、装饰器进阶、正则、标准库;
  7. 实用工具与并发(46-50):requests、多线程、多进程、asyncio、type hints。

学习建议:

  • 不要跳读:Python 里很多语法互相咬合,第 27 篇的 self 会用到第 20 篇讲的参数机制;
  • 一定动手敲:光看不敲,等于没学。每篇文章的代码都可以在自己电脑上运行;
  • 遇到不会不要立即搜答案:先自己想 3 分钟,实在不行再看解释;
  • 给自己布置小项目:学完基础后写一个”猜数字”,学完函数后写一个”待办事项管理”,学完 OOP 后写一个”简易图书管理系统”。

八、常见误区

初学者对 Python 常见有三大误解:

  1. “Python 简单,学个语法就够了。” —— 错。语法只是入场券,工程能力(模块化、异常处理、测试、性能优化)才是分水岭。
  2. “Python 只能做爬虫。” —— 错。爬虫只是最”网红”的方向,Python 在 AI、Web、运维中的地位远超爬虫。
  3. “Python 慢,所以不适合正经项目。” —— 半错。业务逻辑层的性能,Python 完全够用;性能瓶颈通常出在 IO 或算法,用 C 扩展或异步就能解决。Instagram、YouTube、Dropbox 都是 Python 大规模生产的典范。

九、小结与延伸阅读

  • Python 由 Guido van Rossum 于 1989 年发起,1991 年发布;
  • 名字来源于英国喜剧 Monty Python,不是”蟒蛇”;
  • 目前主流版本是 Python 3.x,Python 2 已于 2020 年 EOL;
  • Python 设计哲学强调优雅、显式、可读,写代码要”符合 Python 风格”(Pythonic);
  • 应用极广,是 2025 年最流行的编程语言之一;
  • 学习本系列请按顺序、动手敲代码、结合小项目。

延伸阅读:

下一篇:环境搭建:安装 Python 与配置 IDE。我们要动手安装 Python,把它请到你的电脑上来。