CS61A——Lec-02-函数(含HW-01上)
表达式和值
程序干了些啥
程序通过操纵值来运行;
程序中的表达式求值为值;
表达式:
'd' + 'og'
;值:
'dog'
Python解释器求表达式的值然并显示值
值
每个值都有特定的数据类型:
数据类型 | 示例值 |
---|---|
整数(Integer) | 2 、33 、-1 |
浮点数(Float) | 2.71 、33.0 、-1.9 |
布尔值(Boolean) | True 、False |
字符串(String) | 'dag' 、'33' 、'asdg-!@' |
表达式
每个表达式都描述了一个计算并求值,比如可以使用运算符:
1 | 18 + 72 |
调用表达式
一些表达式调用了函数,比如:
1 | pow(2, 32) |
用运算符的也可以表示为调用函数,比如:
1 | 2 ** 32 |
甚至可以这样:
1 | from operator import add |
区别在于pow()
函数是内置的(built-in),Python环境自带,但是像add
这种就得从标准库中导入(import)进来。
一个调用表达式的分析
Python计算调用表达式的过程:
- 求操作符(Operator)的值;
- 求操作数(Operand)的值;
- 把操作符(函数)应用到操作数(参数)上;
操作符和操作数也都是表达式,所以必须要被求值。
计算嵌套表达式
这就是一个表达式树(Expression Tree)
从外到里调用,从里到外返回值。
命名
命名
一个命名可以绑定到一个值上,一种方法就是声明语句:
1 | x = 1 # x是命名,1是值 |
这个值也可以是表达式:
1 | x = 1 + 2 * 3 - 4 / 5 # x是命名,1 + 2 * 3 - 4 / 5是值 |
使用命名
一个命名可以被引用多次:
1 | x = 10 |
绑定到数据值的命名就叫做变量。
命名重绑定
一个命名只能绑定一个值:
1 | my_name = "3rr0r" |
代码不会保存,但my_name
会绑定新的值3rr0r0r0r
。
练习
下面的代码输出啥?
1 | f = min |
结果为3。
环境图表
环境图表(Environment diagrams)
环境图表用于可视化Python是如何执行程序的。
这个网页可以生成环境图表Online Python Tutor - Composing Programs - Python 3)。
左边一栏箭头指示指令执行的顺序,绿的表示刚执行过了,红色表示马上执行。
右边每个命名绑定到一个值上,一帧内每个命名不能重复。
环境图表中的赋值
Python解释赋值语句的过程:
- 求
=
右边的表达式; - 绑定表达式的值到
=
左边的命名上;
函数
什么是函数
函数是一个代码序列,执行一个特定的任务,可以被重复使用。
比如已经用过的两个函数:
1 | add(18, 69) |
每个函数接受输入(参数,argument)并返回一个输出(返回值,return value)。
定义函数
Python中最常用的定义函数的方法是def
语句:
1 | def <函数名>(<参数列表>): |
例如:
1 | def add(num1, num2): |
定义完了之后,我们可以调用它:
1 | add(2, 2) |
关于函数定义的分析
函数的第一行叫做函数签名(function signature),其后所有的行都是函数体(function body)。
1 | def <name>(<parameters>): # ← 函数签名 |
1 | def <name>(<parameters>): # ← 函数签名 |
函数体可以有很多行:
1 | def add(num1, num2): # ← 函数签名 |
函数参数
可以传递任意表达式作为参数。
例如:
1 | def add(num1, num2): |
返回值
关键词return
会返回一个值给调用函数的地方,并退出函数。
1 | def add(num1, num2): |
也可以在表达式内部调用函数:
1 | big_sum = add(200, 412) + add(312, 256) |
还可以在函数中嵌套函数:
1 | huge_sum = add(add(200, 412), add(312, 256)) |
发现错误
1
2
3
4
5def add(num1, num2):
return sum
sum = num1 + num2
sum = add(2, 4)返回语句之后的语句不会再执行,那一行应该在返回语句之前。
1
2
3
4def add():
return num1 + num2
sum = add(2, 4)函数体中的语句指向了不存在的变量,这里它们应该是函数签名中的参数。
1
2
3
4def add(num1, num2):
sum = num1 + num2
sum = add(2, 4)函数体没有返回值,但是调用它的地方想用它的结果,所以应该有个返回
sum
的语句。
环境图表中的函数
Python解释def
语句的过程:
- 用函数名(name)和参数列表(parameters)创建一个函数;
- 函数体设置为第一行之后的缩进的所有语句;
- 绑定函数名到函数体(类似于赋值语句);
环境图表中的函数调用
Python解释函数调用的过程:
- 在环境中创建新的一帧;
- 绑定函数调用的参数(arguments)到该帧的参数列表(parameters);
- 在新的帧中执行函数体;
关于命名的其他细节
命名和环境
所有的Python代码都在一个环境(environment)的上下文中求值,环境就是帧的序列。
比如前面,调用add
函数的地方就是全局帧(Global frame),进入函数后就是到了函数的局部帧(Local frame),也是全局帧的子帧。
命名查找规则
Python中在用户定义的函数中是怎么查找命名的(简化版本):
- 在局部帧中查找;
- 如果命名不在局部帧中,就到全局帧中查找;
- 如果都不在,那就抛出命名错误(NameError);
命名错误长这样:
命名查找例子
1
2
3
4
5
6def exclamify(text):
start_exclaim = "¡"
end_exclaim = "!"
return start_exclaim + text + end_exclaim
exclamify("the snails are eating my lupines")- 第四行的
start_exclaim
在局部帧中找到; - 第四行的
text
在局部帧中找到,因为调用函数的时候局部变量text
被赋予了值; - 第六行的
exclamify
在全局帧中找到,因为是在全局帧中定义的;
- 第四行的
1
2
3
4
5
6
7start_exclaim = "¡"
end_exclaim = "❣️"
def exclamify(text):
return start_exclaim + text + end_exclaim
exclamify("the voles are digging such holes")- 第五行的
start_exclaim
在全局帧中找到; - 第五行的
text
在局部帧中找到; - 第六行的
exclamify
在全局帧中找到;
- 第五行的
1
2
3
4
5def exclamify(text):
end_exclaim = "⁉️️️"
return start_exclaim + text + end_exclaim
exclamify("the voles are digging such holes")start_exclaim
会导致报错“NameError”,因为它没赋值;- 在调用
exclamify
函数,尝试执行到那条语句的时候才会报错;
总结
- 程序由语句或计算机指令组成,包括描述了如何求值的表达式;
- 值可以赋值到命名上来避免重复计算;
- 赋值语句将表达式的值赋值给当前环境的一个命名;
- 函数封装了一系列将参数映射到返回值的语句;
def
语句会创建一个具有特定参数和函数体的函数对象,并将其绑定到当前环境的一个命名上;- 函数调用表达式将其运算符(函数)的值应用于值或操作数(一些参数)。
Homework
A Plus Abs B
求a
加上b
的绝对值的值,填空:
1 | def a_plus_abs_b(a, b): |
第一个填sub
,第二个填add
。
Two of Three
对于三个正数,返回其中两个最小值的数的平方和,填空,要求不超过一行:
1 | def two_of_three(x, y, z): |
可以填min(x, y) * min(x, y) + min(max(x, y), z) * min(max(x, y), z)
,但是非常不优雅,可以考虑填x**2 + y**2 + z**2 - max(x, y, z)**2
。
虽然看起来更优雅了,但是计算量也更大了(只比较了一次,但计算了四次平方)。