-函数 Lecture 4 - Functions

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
函数作用域闭包高阶函数

-函数 Lecture 4 - Functi

看清函数不只是代码段:从映射、参数传递到闭包的完整运行图谱

按 空格/→ 演示下一步

1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

函数作用域闭包高阶函数

-函数 Lecture 4 - Functi

看清函数不只是代码段:从映射、参数传递到闭包的完整运行图谱

1第 1 页 · -函数 Lecture 4 - Functi

Lecture 4 Introduction

你已经写过无数个函数——def f(x): return x*2,输入 3 永远返回 6。现在要把这种直觉上升为严格定义,看清它的边界。

严格定义
X×Y 的子集,每个 x∈X 恰对应唯一的 y∈Y
记号与三要素
f: X→Y;X 定义域,Y 陪域,f(X)⊆Y 值域
良定义性
全+单值:每个 x 有像且唯一;一对多被禁止
与关系的边界
函数 ⊊ 关系;缺全或缺单值的就不是函数
哈希表对应 →函数

同一 key 永远映射到同一 value;不同 key 可撞同槽(允许多对一)

xX, !yY, (x,y)f\forall x \in X,\ \exists!\, y \in Y,\ (x,y) \in f
2第 2 页 · Lecture 4 Introduction

Creating Functions

你已经会写顺序执行的代码了,但同一段逻辑要反复用怎么办?就像你需要一份可重复使用的菜谱——这就要创建函数。

函数声明
用 function 关键字 + 名字声明,整体被提升到作用域顶部
函数表达式
把函数(匿名或命名)赋给变量,仅变量声明被提升
箭头函数
=> 简写语法,不绑定自己的 this,也没有 arguments 对象
提升差异
声明整体提升;表达式与箭头只能在赋值后调用
写一份菜谱对应 →定义函数

菜谱写一次可反复做菜,函数定义一次可反复调用

3第 3 页 · Creating Functions

Environments

我们写出了函数,但解释器到底怎么知道每个名字指向哪个值?Environments 就是那张「名字→值」的对照表,负责求值时的簿记。

环境 = 帧 + 父指针
一帧是当前作用域下名字到值的绑定,父指针指向外层环境,二者合起来就是一个环境
全局环境
所有顶层定义的落点,是最外层的帧,没有父指针(或父指针为空)
每次调用新建一帧
调用函数时为这次调用的形参与局部变量新建一帧,挂在函数的定义环境之下
查找沿链向外
遇到名字先查当前帧,没找到就沿父指针走到外层环境,直到全局环境为止
嵌套办公室门牌对应 →嵌套环境链

进子房间只看本房间门牌;找不到就探头看父房间,直到最外层门厅

4第 4 页 · Environments

Computing Powers as an E

前面学了怎么定义函数、环境怎么记录绑定。现在用一个经典例子——算 x 的 n 次方——看一个函数定义里究竟藏着什么。

问题:求 xⁿ
输入底数 x 与非负整数 n,输出 x 的 n 次幂
三种解法递进
朴素连乘 → 线性递归 → 快速幂 (x²)^(n/2)
函数封装过程
调用方只看到输入输出;内部怎么算由函数定义负责
同结果不同过程
线性递归与对数递归步数相差几个数量级,却符合同一规格
菜谱对应 →函数定义

菜谱里藏着步骤,函数定义里藏着递归过程;吃饭的人和调用方都只看到最终结果

xn={1n=0xxn1n 奇(x2)n/2n 偶x^n = \begin{cases} 1 & n=0 \\ x \cdot x^{n-1} & n\text{ 奇} \\ (x^2)^{n/2} & n\text{ 偶} \end{cases}
5第 5 页 · Computing Powers as an E

Understanding Variable B

上一页我们看到环境是一张「名字→值」的查找表。那这张表里的条目究竟怎么来的?函数被调用时,参数名怎么就跟具体值绑上了?这一页拆解「绑定」这个动作本身。

绑定是什么
把符号和值用环境条目关联起来,建立一对一对应
何时发生
函数调用时形参绑实参;let/define 求值时名字绑表达式结果
存活期
绑定随所在环境存活;环境消亡,绑定随之消失
词法作用域
自由变量沿词法嵌套向外找最近绑定,而非调用现场
遮蔽
内层同名绑定屏蔽外层;外层仍在,只是暂时不可见
剧本角色名绑定演员对应 →变量绑定

角色名(变量)只在该场戏(作用域)内指代该演员(值);场景结束绑定解除;嵌套场可重绑同名角色

6第 6 页 · Understanding Variable B

Understanding Root Findi

前几页我们写了各种函数:为什么还要专门写函数?最直白的答案之一,是把像「求根」这种反复要做的计算,封装成可复用的黑盒。

根的定义
使 f(x)=0 成立的 x 值,又称零点
几何意义
函数图像与 x 轴交点的横坐标
解析 vs 数值
能公式求解就解析;否则用数值迭代逼近
常见数值法
二分法(稳)、牛顿法(快但要导数)、割线法
应用与陷阱
用于解方程、求交点、找平衡;但可能不收敛、多根、初值敏感
调水温对应 →求根迭代

理想温度对应 f(x)=0;每试一次水温就是一次迭代评估

xn+1=xnf(xn)f(xn)x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}
7第 7 页 · Understanding Root Findi

Modules

函数写多了,一个文件装不下——power()、root()、几十个工具散落各处。模块(module)就是把工具分门别类装进抽屉的方式,让代码可组织、可复用、可按需取用。

模块即文件
任何 .py 文件就是一个模块,文件名即模块名
三种导入
import m、from m import f、from m import f as g
命名空间
模块自带独立命名空间,避免名字冲突
__main__ 守卫
if __name__=='__main__' 让文件既能跑也能被导入
搜索路径
Python 按 sys.path 顺序查找模块,先缓存后磁盘
工具箱对应 →模块

整个箱子=模块;单个工具=函数;拎箱或挑工具=不同导入方式

8第 8 页 · Modules

本节要点

  • 函数是一等公民:可作参数、返回值与数据结构元素
  • 环境帧随调用压栈,形参在其中绑定实参值
  • 名字是绑定而非容器,赋值即重新指向
  • 嵌套函数捕获外层变量名,构成闭包
  • 模块是命名空间,导入仅引入绑定名
延伸主题:Lambda 与高阶函数装饰器与函数包装生成器与惰性求值
9第 9 页 · 本节要点

课后思考

先自己想,再展开参考答案对照思路。

1函数体里写 `x = 5` 和函数体外面写 `x = 5`,两个 `x` 是同一个东西吗?

参考答案不是。函数体里的 `x = 5` 创建的是局部 frame 的绑定;外面的 `x = 5` 改的是当前 frame(通常是全局)。两个 x 分属不同 frame。

2如果对同一个函数名 `f` 连续 def 两次,函数体不一样,调 `f()` 时跑哪个?

参考答案后 def 的覆盖前面的——名字在当前 environment 里只指向最新绑定。这和普通变量重新赋值完全同构。

3在 A 模块 `def f()`,B 里 `from A import f` 后再改 A.f,B 里的 f 会变吗?

参考答案不会。`from A import f` 在那一刻把函数对象绑到 B 的 f 名字上,之后 A 里改名或重新 def 都不影响 B 已持有的引用。

10第 10 页 · 课后思考
-函数 Lecture 4 - Functions · 知识图解