CS61A——Lec-02-函数(含HW-01上)

表达式和值

程序干了些啥

  1. 程序通过操纵来运行;

  2. 程序中的表达式求值为

    表达式:'d' + 'og';

    值:'dog'

  3. Python解释器求表达式的值然并显示值

每个值都有特定的数据类型:

数据类型 示例值
整数(Integer) 233-1
浮点数(Float) 2.7133.0-1.9
布尔值(Boolean) TrueFalse
字符串(String) 'dag''33''asdg-!@'

表达式

每个表达式都描述了一个计算并求值,比如可以使用运算符:

1
2
3
4
18 + 72
6 / 29
2 * 10
2 ** 32

调用表达式

一些表达式调用了函数,比如:

1
2
3
pow(2, 32)
max(2, 2048)
min(-1, -7)

用运算符的也可以表示为调用函数,比如:

1
2
2 ** 32
pow(2, 32)

甚至可以这样:

1
2
3
4
from operator import add

18 + 69
add(18, 69)

区别在于pow()函数是内置的(built-in),Python环境自带,但是像add这种就得从标准库中导入(import)进来。

一个调用表达式的分析

image-20220102024904177

Python计算调用表达式的过程:

  1. 求操作符(Operator)的值;
  2. 求操作数(Operand)的值;
  3. 把操作符(函数)应用到操作数(参数)上;

操作符和操作数也都是表达式,所以必须要被求值。

计算嵌套表达式

image-20220102025249148

这就是一个表达式树(Expression Tree)

从外到里调用,从里到外返回值。

命名

命名

一个命名可以绑定到一个值上,一种方法就是声明语句:

1
x = 1 # x是命名,1是值

这个值也可以是表达式:

1
x = 1 + 2 * 3 - 4 / 5 # x是命名,1 + 2 * 3 - 4 / 5是值

使用命名

一个命名可以被引用多次:

1
2
3
4
5
x = 10
y = 3

result1 = x * y
result2 = x + y

绑定到数据值的命名就叫做变量。

命名重绑定

一个命名只能绑定一个值:

1
2
my_name = "3rr0r"
my_name = my_name + "0r0r"

代码不会保存,但my_name会绑定新的值3rr0r0r0r

练习

下面的代码输出啥?

1
2
3
4
5
6
f = min
f = max
g = min
h = max
max = g
max(f(2, g(h(1, 5), 3)), 4)

结果为3

环境图表

环境图表(Environment diagrams)

环境图表用于可视化Python是如何执行程序的。

这个网页可以生成环境图表Online Python Tutor - Composing Programs - Python 3)。

image-20220102161519234

左边一栏箭头指示指令执行的顺序,绿的表示刚执行过了,红色表示马上执行。

右边每个命名绑定到一个值上,一帧内每个命名不能重复。

环境图表中的赋值

Python解释赋值语句的过程:

  1. =右边的表达式;
  2. 绑定表达式的值到=左边的命名上;

函数

什么是函数

函数是一个代码序列,执行一个特定的任务,可以被重复使用。

比如已经用过的两个函数:

1
2
add(18, 69)
mul(60, sub(5, 4))

每个函数接受输入(参数,argument)并返回一个输出(返回值,return value)。

定义函数

Python中最常用的定义函数的方法是def语句:

1
2
def <函数名>(<参数列表>):
return <return 表达式>

例如:

1
2
def add(num1, num2):
return num1 + num2

定义完了之后,我们可以调用它:

1
2
add(2, 2)
add(18, 69)

关于函数定义的分析

函数的第一行叫做函数签名(function signature),其后所有的行都是函数体(function body)。

1
2
def <name>(<parameters>):        # ← 函数签名
return <return expression> # ← 函数体
1
2
def <name>(<parameters>):        # ← 函数签名
return <return expression> # ← 函数体

函数体可以有很多行:

1
2
3
def add(num1, num2):             # ← 函数签名
sum = num1 + num2 # ← 函数体
return sum # ← 函数体

函数参数

可以传递任意表达式作为参数。

例如:

1
2
3
4
5
6
7
8
9
def add(num1, num2):
return num1 + num2

x = 1
y = 2
add(x, y)

x = 3
add(x * x, x + x)

返回值

关键词return会返回一个值给调用函数的地方,并退出函数。

1
2
3
4
def add(num1, num2):
return num1 + num2

sum = add(2, 4)

也可以在表达式内部调用函数:

1
big_sum = add(200, 412) + add(312, 256)

还可以在函数中嵌套函数:

1
huge_sum = add(add(200, 412), add(312, 256))

发现错误

  1. 1
    2
    3
    4
    5
    def add(num1, num2):
    return sum
    sum = num1 + num2

    sum = add(2, 4)

    返回语句之后的语句不会再执行,那一行应该在返回语句之前。

  2. 1
    2
    3
    4
    def add():
    return num1 + num2

    sum = add(2, 4)

    函数体中的语句指向了不存在的变量,这里它们应该是函数签名中的参数。

  3. 1
    2
    3
    4
    def add(num1, num2):
    sum = num1 + num2

    sum = add(2, 4)

    函数体没有返回值,但是调用它的地方想用它的结果,所以应该有个返回sum的语句。

环境图表中的函数

Python解释def语句的过程:

  1. 用函数名(name)和参数列表(parameters)创建一个函数;
  2. 函数体设置为第一行之后的缩进的所有语句;
  3. 绑定函数名到函数体(类似于赋值语句);

image-20220102165146936

环境图表中的函数调用

Python解释函数调用的过程:

  1. 在环境中创建新的一帧
  2. 绑定函数调用的参数(arguments)到该帧的参数列表(parameters);
  3. 在新的帧中执行函数体;

image-20220102165512600

关于命名的其他细节

命名和环境

所有的Python代码都在一个环境(environment)的上下文中求值,环境就是帧的序列。

比如前面,调用add函数的地方就是全局帧(Global frame),进入函数后就是到了函数的局部帧(Local frame),也是全局帧的子帧。

命名查找规则

Python中在用户定义的函数中是怎么查找命名的(简化版本):

  1. 在局部帧中查找;
  2. 如果命名不在局部帧中,就到全局帧中查找;
  3. 如果都不在,那就抛出命名错误(NameError);

命名错误长这样:

image-20220102170356438

命名查找例子

  1. 1
    2
    3
    4
    5
    6
    def exclamify(text):
    start_exclaim = "¡"
    end_exclaim = "!"
    return start_exclaim + text + end_exclaim

    exclamify("the snails are eating my lupines")
    1. 第四行的start_exclaim在局部帧中找到;
    2. 第四行的text局部帧中找到,因为调用函数的时候局部变量text被赋予了值;
    3. 第六行的exclamify在全局帧中找到,因为是在全局帧中定义的;
  2. 1
    2
    3
    4
    5
    6
    7
    start_exclaim = "¡"
    end_exclaim = "❣️"

    def exclamify(text):
    return start_exclaim + text + end_exclaim

    exclamify("the voles are digging such holes")
    1. 第五行的start_exclaim在全局帧中找到;
    2. 第五行的text在局部帧中找到;
    3. 第六行的exclamify在全局帧中找到;
  3. 1
    2
    3
    4
    5
    def exclamify(text):
    end_exclaim = "⁉️️️"
    return start_exclaim + text + end_exclaim

    exclamify("the voles are digging such holes")
    1. start_exclaim会导致报错“NameError”,因为它没赋值;
    2. 在调用exclamify函数,尝试执行到那条语句的时候才会报错;

总结

  1. 程序由语句或计算机指令组成,包括描述了如何求值的表达式
  2. 可以赋值到命名上来避免重复计算;
  3. 赋值语句将表达式的值赋值给当前环境的一个命名;
  4. 函数封装了一系列将参数映射到返回值的语句;
  5. def语句会创建一个具有特定参数函数体的函数对象,并将其绑定到当前环境的一个命名上;
  6. 函数调用表达式将其运算符(函数)的值应用于值或操作数(一些参数)。

Homework

A Plus Abs B

a加上b的绝对值的值,填空:

1
2
3
4
5
6
7
8
9
10
11
12
13
def a_plus_abs_b(a, b):
"""Return a+abs(b), but without calling abs.

>>> a_plus_abs_b(2, 3)
5
>>> a_plus_abs_b(2, -3)
5
"""
if b < 0:
f = _____
else:
f = _____
return f(a, b)

第一个填sub,第二个填add

Two of Three

对于三个正数,返回其中两个最小值的数的平方和,填空,要求不超过一行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def two_of_three(x, y, z):
"""Return a*a + b*b, where a and b are the two smallest members of the
positive numbers x, y, and z.

>>> two_of_three(1, 2, 3)
5
>>> two_of_three(5, 3, 1)
10
>>> two_of_three(10, 2, 8)
68
>>> two_of_three(5, 5, 5)
50
"""
return _____

可以填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

虽然看起来更优雅了,但是计算量也更大了(只比较了一次,但计算了四次平方)。