代码语言

知识点思维导图

21 个知识节点

Python(05) - 控制流与函数

读完后,你应能完成以下任务:

  • 绘制“Python(05) - 控制流与函数 / 前端锚点”的关键对象与数据流,解释“for 默认就是 for...of(遍历值,不是索引)。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(05) - 控制流与函数 / 控制流:先划清和 JS 的差异”设计正常与异常输入,验证“这是 Python 第一眼最大的不同。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(05) - 控制流与函数 / for:默认是 for...of,要索引用 enumerate”的最小代码或配置,检验“Python 的 for 没有 for (let i = 0; ...) 这种 C 式写法,默认就是遍历值,等价于 JS 的 for...of。”,输出命令、结果与 Diff,并说明不适用边界。

控制流(if/for/while)你在 JS 里已经写过无数遍,这篇只需关注「哪里不一样」。真正的重点是 Python 的函数:def、默认参数、*args/**kwargs——这套参数玩法比 JS 灵活得多,也是后面读 FastAPI / openai SDK 源码绕不开的基础。

一、前端锚点

控制流部分,Python 和 JS 心智模型几乎一致,差异只在「语法皮肤」:没有花括号,靠缩进分块;for 默认就是 for...of(遍历值,不是索引)。

函数部分差异更大,先记住一句类比:Python 的函数参数 ≈ JS 函数参数 + 默认值 + 解构 + 剩余参数,但全部内置且更显式*args ≈ JS 的 ...rest**kwargs ≈ 把一个对象「展开」成具名参数。


二、控制流:先划清和 JS 的差异

2.1 没有花括号,缩进就是代码块

这是 Python 第一眼最大的不同。JS 用 {} 圈代码块,Python 用冒号 + 缩进

// JavaScript:花括号分块
if (age >= 18) {
  console.log("成年")
} else {
  console.log("未成年")
}

关键差异:

  • 行尾必须有 :,下一行必须缩进(统一 4 空格,别和 Tab 混用,否则报 IndentationError
  • 缩进是语法不是风格,缩进错了直接报错(JS 缩进只是好看)
  • 没有分号(写了也不报错,但不符合习惯)

2.2 条件判断:elif、布尔运算符是英文单词

// JavaScript
if (score >= 90) {
  grade = "A"
} else if (score >= 60) {
  grade = "B"
} else {
  grade = "C"
}
const ok = isVip && age > 18 || isAdmin  // && || !

对照表:

JS/TS Python 说明
else if elif 合并成一个关键字
&& and 逻辑与
|| or 逻辑或
!x not x 逻辑非
a === b a == b Python 无 ===== 即比值
cond ? a : b a if cond else b 三元,顺序不同

注意三元表达式的语序——Python 把「真值」写在最前面:

2.3 for:默认是 for...of,要索引用 enumerate

Python 的 for 没有 for (let i = 0; ...) 这种 C 式写法,默认就是遍历值,等价于 JS 的 for...of

// JavaScript
const fruits = ["apple", "banana", "cherry"]
for (const f of fruits) {
  console.log(f)
}
for (let i = 0; i < 5; i++) {
  console.log(i)
}

需要同时拿索引和值时,用内置的 enumerate,对标 JS 的 arr.entries()

// JavaScript:要索引时
for (const [i, f] of fruits.entries()) {
  console.log(i, f)
}

遍历字典(≈ JS 对象)时,用 .items() 同时拿键值:

需求 JS Python
遍历值 for (const x of arr) for x in arr
遍历 N 次 for (let i=0; i<n; i++) for i in range(n)
带索引 arr.entries() enumerate(arr)
遍历对象键值 Object.entries(obj) dict.items()

2.4 while:基本一样,但没有 do-while

注意:Python 没有 ++ / --,也没有 do...while。需要「至少执行一次」时,用 while True + break 模拟。


三、函数 def:重点章节

3.1 基本定义

// JavaScript
function add(a, b) {
  return a + b
}
const add2 = (a, b) => a + b   // 箭头函数

关键差异:

  • def,函数体靠缩进
  • lambda 是 Python 的「箭头函数」,但只能放一个表达式,不能写多行逻辑——所以 Python 里写复杂逻辑一律用 def,lambda 只用于 sorted(key=...) 这类一次性小函数

3.2 类型注解(可选,但后端常写)

Python 支持类型注解,长得像 TS,但默认不强制检查(运行时不报错,靠 IDE / mypy / Pydantic 检查)。FastAPI 正是靠这套注解做参数校验。

// TypeScript:类型是强制的
function greet(name: string): string {
  return `Hello, ${name}`
}
TS Python
name: string name: str
age: number age: int / age: float
flag: boolean flag: bool
: string(返回值) -> str(返回值)
`Hello ${name}` f"Hello {name}"

注:这套注解在后面 FastAPI 篇会大量出现,那里类型注解会被「升级」成真正的请求参数校验。详见 FastAPI 相关章节。

3.3 默认参数

和 JS 一样支持默认值,但有一个致命坑(见第四节)。

// JavaScript
function createUser(name, role = "user") {
  return { name, role }
}

3.4 关键字参数(Python 独有的爽点)

Python 调用函数时可以用参数名传参,顺序随意。这是 JS 没有的(JS 只能靠传一个对象模拟)。

// JavaScript:想要「具名传参」只能传对象
function connect({ host, port, timeout = 30 }) { /* ... */ }
connect({ port: 5432, host: "localhost" })   // 解构对象

这个特性让你读 SDK 调用时一眼看懂每个值的含义,例如:


四、args 和 kwargs:收集任意参数

这是本篇最该花时间的概念,也是读第三方库源码(到处是 def foo(*args, **kwargs))的前提。

4.1 args:收集多余的「位置参数」成元组

*args ≈ JS 的剩余参数 ...rest,把多传的位置参数打包成一个元组

// JavaScript:剩余参数
function sum(...nums) {       // nums 是数组
  return nums.reduce((a, b) => a + b, 0)
}
sum(1, 2, 3)   // 6

名字 args 只是约定,关键是前面的 **nums*items 都行。

4.2 kwargs:收集多余的「关键字参数」成字典

**kwargs 把所有「名字=值」形式的多余参数打包成一个字典。JS 没有直接对应物,最接近的是「把剩下的属性收进一个对象」。

// JavaScript:用对象 + 剩余属性近似
function request(url, { method = "GET", ...options } = {}) {
  console.log(url, method, options)   // options 收集其余配置
}
request("/api", { method: "POST", timeout: 30, retry: 3 })

4.3 两者组合 + 完整参数顺序

参数定义顺序是固定的,记成一句话:位置参数(可带默认值)→ *args → 关键字参数(可带默认值)→ **kwargs。注意 *args 之后定义的参数会变成「只能用关键字传」(keyword-only),比如下面综合示例里 *headers 后面的 timeout 就只能写成 timeout=60。最常见的组合是把 *args**kwargs 放最后,表示「接收任意参数」:

4.4 反向操作: 和 用于「解包」

* / ** 出现在调用处时,作用相反——把列表/字典「拆开」成一个个参数。这正是 JS 展开运算符 ... 的两种用法合体。

// JavaScript:展开运算符
const nums = [1, 2, 3]
Math.max(...nums)              // 数组展开成参数

const config = { host: "x", port: 1 }
connect({ ...config })         // 对象展开

记忆口诀:

  • 定义函数时*args/**kwargs = 收集(多个值 → 一个集合)
  • 调用函数时*list/**dict = 解包(一个集合 → 多个值)

五、前端新手最易踩的坑

5.1 坑 1:默认参数不要用可变对象(list/dict)

JS 里默认值每次调用都重新求值,Python 默认值只在函数定义时求值一次,可变默认值会被多次调用「共享」,造成数据串台。

5.2 坑 2:缩进必须统一,别混用 Tab 和空格

Python 缩进是语法。同一个块里混用 Tab 和空格会报 TabError 或逻辑错乱。统一用 4 个空格(编辑器设置 Tab 转空格即可)。

5.3 坑 3:函数默认返回 None,不是 undefined

JS 函数不写 return 返回 undefined;Python 返回 None。判空时用 is None,别用 == None

5.4 坑 4:别拿 lambda 当箭头函数写复杂逻辑

lambda 只能容纳一个表达式,不能写 if/for 语句块、不能多行。需要逻辑就老老实实 def。lambda 的正确用途是一次性小函数:

5.5 坑 5:range 是左闭右开,且不含 C 式 for

range(5)0~4range(2, 5)2,3,4——和 JS for(i=0;i<5;i++) 的边界一致(右开),但别再想着写 i++


六、综合示例:一个带各种参数的函数


七、总结

  • 前端锚点:for 默认就是 for...of(遍历值,不是索引)。
  • 控制流:先划清和 JS 的差异:这是 Python 第一眼最大的不同。
  • 函数 def:重点章节:lambda 是 Python 的「箭头函数」,但只能放一个表达式,不能写多行逻辑——所以 Python 里写复杂逻辑一律用 def,lambda 只用于 sorted(key=...) 这类一次性小函数
  • args 和 kwargs:收集任意参数:这是本篇最该花时间的概念,也是读第三方库源码(到处是 def foo(args, kwargs))的前提。
  • 前端新手最易踩的坑:Python 缩进是语法。
  • 没有花括号,缩进就是代码块:这是 Python 第一眼最大的不同。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“控制流与函数”中,需要同时满足“前端锚点”与“没有花括号,缩进就是代码块”。给定正文约束“for 默认就是 for...of(遍历值,不是索引)。”,哪些判断保持了原有处理机制?多选
2“控制流与函数”出现偏差:“在“控制流与函数 / 条件判断:elif、布尔运算符是英文单词”中,即使不满足“注意三元表达式的语序——Python 把「真值」写在最前面”,结果与副作用仍会保持不变。”已成为实际行为。围绕“条件判断:elif、布尔运算符是英文单词”与“for:默认是 for...of,要索引用 enumerate”,哪些判断能定位被改变的职责或边界?多选
3评审“控制流与函数”方案时,验收条件包含“需要「至少执行一次」时,用 while True + break 模拟。”。关于“while:基本一样,但没有 do-while”与“基本定义”的哪些决策符合正文机制?多选