上一篇文章中,我们了解了代码的分支结构(if 家族语句)和循环结构(for 循环和 while 循环)。通过了解这些结构,我们已经能够写出稍微复杂一些的代码。但当代码一多,就会遇到一些问题。
上一篇文章中有个案例:根据考试分数打印安全意识分级(优秀、及格和不及格)的代码,如下所示:
a = 75
if a > 80:
print("优秀")
elif a > 60:
print("及格")
else:
print("不及格")
如果我们现在需要一次性看三位同学的分级结果,比如 a 75 分,b 90 分,c 66 分,然后打印出每个人的分级信息。根据我们之前的知识,很自然就想到,针对每个变量都执行一次上面的 if-elif-else 语句即可,完整的代码是这样的:
a = 75
b = 90
c = 66
if a > 80:
print("优秀")
elif a > 60:
print("及格")
else:
print("不及格")
if b > 80:
print("优秀")
elif b > 60:
print("及格")
else:
print("不及格")
if c > 80:
print("优秀")
elif c > 60:
print("及格")
else:
print("不及格")
最终输出是及格、优秀、及格,分别代表 a、b、c 三位同学的分级结果。
及格
优秀
及格
结果是计算出来了,但回过头去看会发现明明看着很像,却愣是重复写了三次。有没有更好的方式来避免重复写多次结构类似的代码呢?而且这个例子,重复的部分还只有 6 行,要是一个计算过程有 50 行,然后要针对一个班级 50 个同学都执行一次,难道我们需要写 50 x 50 = 2500 行代码吗?
答案是肯定的,在 Python 中相似结构的代码复用通过函数来实现。了解了函数,我们才能真正意义上编写复杂的代码。
看到函数,可能你首先会条件反射地想到数学中的“函数”,但 Python 中的函数和数学中的不是一回事,不需要联合起来理解。
Python 中的函数简单来说就是一段有名字的代码块。通过函数的机制,我们可以给我们希望重复使用的代码块起个名字,这样我们之后要用这个代码块的时候,就不需要重新写一遍一模一样的代码块,而只需要简单写一次之前给代码块起的名字即可。普通函数的形式如下所示:
def 函数名():
代码块
其中的关键要素是:
当我们创建完函数之后,要实际执行函数中的代码块,还需要执行该函数。函数执行的形式比较简单:函数名之后加一个括号即可。形式如下:
函数名()
现在我们用一个简单的例子来感受函数的用处。假设我们需要使用字符 A 打印一个简单的三角形。
print(" A")
print(" AAA")
print("AAAAA")
输出:
A
AAA
AAAAA
现在我们想打印三个,虽然我们可以用类似本文开头的方式,把上面的代码复制出三份。但现在我们用函数的方式来解决。新建 Cell ,输入如下代码:
def print_triangle():
print(" A")
print(" AAA")
print("AAAAA")
执行上述代码,发现并没有任何内容输出。原因就是目前我们只是创建了函数(给代码块起了名字),但还没有实际执行它。
现在我们来执行一下,新建 Cell,输入以下代码:
print_triangle()
执行代码,输出结果就可以看到我们的代码块被成功执行了。
A
AAA
AAAAA
说到这里,相信你已经知道怎么用更方便的方式打印三个三角形了,答案就是执行三次 print_triangle 函数。
print_triangle()
print_triangle()
print_triangle()
输出是这样的:
A
AAA
AAAAA
A
AAA
AAAAA
A
AAA
AAAAA
通过上述例子可以看到,我们可以通过函数的形式给代码块起一个名字,之后只需要在代码中执行这个函数就可以起到和执行代码块一样的效果。这样的实现方式可以减少整体的代码量,也能够让代码整体更加清晰。
现在我们回过头去看开篇遇到的给三个同学的分数分级的问题。我们之前通过三个 if-elif-else 语句来实现,现在尝试使用函数来优化这个代码。初步形式如下所示:
def print_level():
if a > 80:
print("优秀")
elif a > 60:
print("及格")
else:
print("不及格")
细心的你很快就发现了问题……这个函数每次都只检查了 a 同事的分数,但我们的任务是要分别检查 a、b、c 三个同事的分数,怎么办?
换句话说,我们希望打印 a 同学分级时,这段代码的 if 语句 判断的是 a,打印 b 同学的分级时,这段代码的 if 语句判断的是 b。简单来说, if 语句判断的这个变量,不能在代码块中把他写死,而是应该在执行函数的时候传进来。
在 Python 中,我们可以使用函数的参数来解决这个问题。我们可以把需要执行阶段传入的变量写在函数名后面的括号中,需要几个写几个。这样的变量就被称为参数。形式如下:
def 函数名(参数1, 参数2, ...):
代码块
老规矩,我们举一个非常简单的例子加深理解。假设我们编写一个函数:打印某个数 +3 的结果。这里的“某个数”,我们用参数传入。
函数定义
def print_out(a):
print(a + 3)
print_out(3)
输出
6
我们来剖析这里面发生了什么:
我们还是通过例子来理解,我们希望 print_level 函数能够根据需要处理不同的分数,比如第一次执行,处理的是 a 同学的分数,第二次是 b 同学。那么我们可以把对应代码块中的变量变成参数,假设取名为 score。
新的函数代码如下:
def print_level(score):
if score > 80:
print("优秀")
elif score > 60:
print("及格")
else:
print("不及格")
与之前实现的主要区别就是,我们不再直接判断某个同学的分数(比如之前的变量 a)。而是在函数名的括号中声明了一个参数 score,之后在函数体中就直接针对 score 进行判断。
然后执行上述代码,确保函数创建成功。
接下来就到了函数执行的环节,我们希望可以用该函数分别处理三个同学的分数,那是不是就是执行三次函数,然后每次的参数就写对应 a、b、c 同事的分数就可以了呢?你可以先思考一下,然后再看实现的代码。
a = 75
b = 90
c = 66
print_level(a)
print_level(b)
print_level(c)
输出可以看到,执行的结果和我们写三个很长的 if-elif-else 语句是一样的。
及格
优秀
及格
上例中的 score,我们称之为形式参数,也叫形参。形式参数顾名思义,是声明了一个变量,给函数体中写逻辑用的,上例中我们的逻辑就是通过对形式参数 score 进行判断写的。
上例中的 a、b、c,我们称之为实际参数,也叫实参。实际参数就是指函数实际执行的时候,函数的形式参数的值。比如当执行 print_level(a) 时,这个时候执行到函数里面,形式参数 score 的值就等于 a ,也就是 75。
我们再通过一个例子加深印象。假设我们需要编写一个函数,实现打印学生的基本信息。格式如下:
姓名:小明
年龄:12
班级:九年一班
通过分析可以发现,打印前缀的提示,比如“姓名”“年龄”这些是不变的,而具体每个学生的姓名、年龄以及班级信息都是会随着学生而变化的。那显而易见,这里我们需要三个参数。
def print_info(name, age, title):
print("姓名:" + name)
print("年龄:" + str(age))
print("班级:" + title)
上述代码有几个注意的点。
执行上述代码,并新建 cell,添加下面的代码测试一下。
print_info("小明",11, "九年一班")
print_info("小红",12, "九年二班”)
输出如下:
姓名:小明
年龄:11
编制:九年一班
姓名:小红
年龄:12
编制:九年二班
可以看到我们通过两次调用 print_info 函数,并传入不同的信息,实现了打印不同学生信息的功能。
总结一下,当函数的代码需要处理每次执行都可能会变化的变量时,可以将这些变量声明为形参,放在函数名后面的括号里。然后在函数实际执行的时候,根据我们希望函数处理的变量以实参的形式传递给函数。
通过函数的参数机制,本质上我们实现了可以向函数发送信息的本事(函数参数其实就是在函数在执行的时候,外部代码发送给函数内部代码的值)。另一方面,函数内部是否可以向外部代码发送信息呢?
从一个具体的例子来说,刚才我们打印了 a、b、c 三个同事的分级结果。现在需要编写函数,统计三个 t 同学的考试分数的平均值。
问题分析:计算三个同学分数的平均值,本质上就是计算三个数字的平均值。那代表这个函数需要接受 3 个参数(因为可能换另外三个人,那三个成绩就都不一样了,所以在这里会有变化的变量有 3 个)。
另外,因为是求三个数字的平均值,我们就起个函数名叫:three_average。基于此,我们可以编写如下的函数:
def three_average(score1, score2, score3):
result = (score1 + score2 + score3) / 3
代码也比较容易理解,我们声明了三个参数:score1、score2、score3 。然后在函数中我们计算了他们的均值并存储在变量 result 中。
老规矩,执行上面的 cell,并新建 Cell 输入以下的测试代码。
three_average(a, b, c)
print(result)
我们执行了 three_avarage 函数,并将 a、b、c 三名同事的成绩作为实参传递给它,之后我们尝试打印出 result 变量的值。
运行代码,报了如下的错误:
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-36-e489956d03fc> in <module>
1 three_average(a, b, c)
----> 2 print(result)
NameError: name 'result' is not defined
错误提示说的是:变量名 result 未定义。
回过头去看 three_average 函数,我们的 result 变量是在函数内部被赋值的。所以在函数外面是用不了了。这里涉及一个新的知识点:在 Python 中,函数内部创建、赋值的变量,仅在函数内部有效。
那现在问题来了,我们计算的 result 如何告诉给调用 three_average 的代码?这个问题如下所示
three_average(a, b, c)
print(???) <---------这里该 print 什么?怎么拿到函数内部计算的均值?
Python 中,通过 return 关键字来实现将函数内部的值返回给调用函数的代码。形式如下:
def 函数名(参数1, 参数2, ...):
代码块
return 返回值
新增了以下要素。
函数内部通过 return 语句返回了内容,执行函数的时候怎么拿到呢?我们可以在执行函数的时候,将执行函数的代码放在等号的左边,右边是一个变量,这样就实现了把函数的结果,赋值给了一个变量。
形式像这样:
变量 = 函数名(参数1, 参数2,...)
现在,我们使用 return 语句来解决我们在本章开头的问题,重写 three_average 函数。
def three_average(score1, score2, score3):
result = (score1 + score2 + score3) / 3
return result
final_score = three_average(a, b, c)
print(final_score)
新版本主要有以下改动点:
输出结果为 77。
77.0
大功告成,我们的 three_average 函数在得到 return 语句的加持之后,才能变成真正意义上好用的函数。
在思考一个函数该如何实现的时候,我们可以遵循以下的三段论。
现在我们用三段论来思考一下这个实战。
明确需求:把学生的分数转换为分级,并将分级返回给外部,不用打印。
明确输入:学生的分数。
明确输出:分级的结果。
根据上面的分析,以及 return 语句的配合,很容易想出我们只需要将最开始的 print_level 的实现中,打印的代码换成 return 语句即可实现将分级结果返回给外部。另外,由于我们并不会打印分级信息而是返回,所以我们新的函数取名为 get_level。实现如下:
def get_level(score):
if score > 80:
return "优秀"
elif score > 60:
return "及格"
else:
return "不及格"
接下来编写代码来调用 get_level,测试一下功能是否正常
a = get_level(50)
b = get_level(65)
c = get_level(90)
print(a,b,c)
输出
不及格 及格 优秀
说明我们 get_level 函数被成功执行,并返回了分级的结果,分别存储在了 a,b,c 三个变量上。
关于 Python 函数的关键特性在上面已经基本介绍完毕了。Python 代码中,我们一般主要打交道的函数有两种类型:
很明显,我们在这一课中定义的 print_level、print_info 以及 three_average,list_average 函数都属于用户自定义函数。而我们一直以来使用的:
本质都是 Python 的库函数。
现在我们就用函数的视角,来分析一下 len 和 print 的功能
Python 的库函数非常多,感兴趣的可以到 Python 的官方文档库浏览学习。