Python 的控制流和 C/Java 同构(if/for/while),但有三处「身份差异」:缩进即语法、for 是遍历不是计数、函数参数系统复杂到需要专节。本章把这三处讲透,顺带拿下 Python 最有辨识度的语法:推导式(comprehension)。
C/Java 用花括号划块,Python 用缩进(4 空格,PEP 8 标准):
score = 85
if score >= 90:
print("优秀")
elif score >= 60:
print("及格") # 缩进层级 = 代码块
else:
print("不及格")
for i in range(5): # range(5) = 0,1,2,3,4(含头不含尾,和切片一致)
print(i)
冒号开启块,缩进定义块,混用 Tab 和空格直接报错(TabError)。反对者说「强制缩进是多管闲事」,支持者说「反正大家都得缩进,不如让机器管」;工程事实是:Python 代码的全局可读性确实高于花括号语言(软件工程课第 7 章「代码质量」的先赚一步)。
for 循环的正确心智:for 是「对每个元素」(foreach),不是「数数」。要下标用 enumerate:
for i, ch in enumerate("abc"): # (0,'a'), (1,'b'), (2,'c')
print(i, ch)
for a, b in zip([1,2], ["x","y"]): # 并行遍历:(1,'x'), (2,'y')
print(a, b)
while 同 C;break/continue 同 C;Python 独有 for-else(循环正常跑完(没 break)则执行 else,搜索类代码「找到了 break,没找到才走 else」很顺手)。
Python 最有辨识度的语法,把「循环 + 过滤 + 收集」压成一行:
nums = [1, 2, 3, 4, 5, 6]
squares = [n * n for n in nums] # [1,4,9,16,25,36]
evens = [n for n in nums if n % 2 == 0] # [2,4,6](带过滤)
matrix = [[1,2],[3,4]]
flat = [x for row in matrix for x in row] # [1,2,3,4](双层,先外后内)
sq_map = {n: n*n for n in nums} # 字典推导式 {1:1, 2:4, ...}
unique = {x % 3 for x in nums} # 集合推导式 {0, 1, 2}
lazy = (n * n for n in nums) # 生成器表达式:不占内存(第 8 章)
读法:[表达式 for 变量 in 可迭代 if 条件],「对每个满足条件的元素,算出表达式,收集成列表」。它不只是省行数:「目的(要什么)在前,过程(怎么遍历)在后」,可读性高于等价 for 循环——这是 Pythonic 的第一课。纪律:推导式超过两层嵌套就该拆回 for(可读性优先)。
def bmi(weight, height):
"""计算 BMI。docstring:第一行写干什么。"""
return weight / height ** 2
print(bmi(70, 1.75)) # 22.86
print(bmi.__doc__) # 文档字符串可查
help(bmi) # REPL 里看帮助
函数用 def 定义,return 返回(可返回多个值!实为打包元组:return lo, hi,调用处 lo, hi = f() 解包——Java 课第 6 章「单返回值 + 出参」的 Python 答案:直接多返回)。docstring 是函数的说明书(第 7 章 help() 的数据源),养成习惯。作用域规则 LEGB(Local → Enclosing → Global → Built-in,由内向外找);函数内读全局变量可以,赋值需要 global 声明(否则 Python 当你新建了局部变量——常见坑)。
Python 的参数系统是它最「丰富」的部分,五件套:
def f(a, b=2, *args, **kwargs): # 位置必选 | 默认值 | 可变位置 | 可变关键字
...
f(1) # a=1
f(1, 5) # a=1, b=5
f(1, 5, 6, 7) # args=(6, 7) 收集成元组
f(1, x=9, y=8) # kwargs={'x':9, 'y':8} 收集成字典
f(*[1,2], **{"z": 3}) # 反向:列表/字典拆开当参数
f(b=5, a=1) 指名传,可读性爆炸(「布尔参数地雷」f(True, False) 的解毒剂)。可变默认参数大坑单独立牌:
def add(item, lst=[]): # 危险!默认列表在 def 时创建一次
lst.append(item)
return lst
add(1) # [1]
add(2) # [1, 2] ← 不是 [2]!两次调用共享同一个列表
def add_ok(item, lst=None): # 正确姿势:None 哨兵
if lst is None:
lst = []
lst.append(item)
return lst
原因在第 2 章「名字贴」:默认值是 def 时求值一次的对象,函数对象一直贴着它。规则:默认值永远用不可变对象(数字、字符串、None)。
函数可以赋值、传参、当返回值(第 2 章「皆对象」的兑现):
def shout(s): return s.upper()
def whisper(s): return s.lower()
def speak(style): # 高阶函数:函数当参数/返回值
return {"loud": shout, "quiet": whisper}[style]
speak("loud")("hi") # 'HI'
这是第 8 章(高阶函数、装饰器)和第 11 章 GUI(回调)的地基。Java 课第 11 章的 Lambda 在 Python 里就是普通函数(本来就是对象)。
for x in lst[:]:)或用列表推导式重建。{"apple": 5, "banana": 6}(字典推导式,len)。def stats(nums) 一次性返回 (最小值, 最大值, 平均值),用元组多返回;对比 Java 版(需要出参或建类)的代码量。id(lst) 验证两次调用同一对象;用 None 哨兵修复。