likes
comments
collection
share

快乐学Python,Python基础之代码复用?「函数」

作者站长头像
站长
· 阅读数 6

上一篇文章中,我们了解了代码的分支结构(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 中相似结构的代码复用通过函数来实现。了解了函数,我们才能真正意义上编写复杂的代码。

1、什么是函数

看到函数,可能你首先会条件反射地想到数学中的“函数”,但 Python 中的函数和数学中的不是一回事,不需要联合起来理解。

Python 中的函数简单来说就是一段有名字的代码块。通过函数的机制,我们可以给我们希望重复使用的代码块起个名字,这样我们之后要用这个代码块的时候,就不需要重新写一遍一模一样的代码块,而只需要简单写一次之前给代码块起的名字即可。普通函数的形式如下所示:

def 函数名():

    代码块

其中的关键要素是:

  • def, Python 的关键字,代表我们接下来要创建一个函数;
  • 函数名,顾名思义,函数的名字,名字需要符合变量起名的定义;
  • 冒号,大家应该很熟悉了,这代表冒号后面的代码块的名字是冒号前面的【函数名】;
  • 代码块,也就是我们想起名字来指代的代码块,这种函数对应的代码块,我们也叫函数体。注意这里的代码块和 if 语句的代码块一样,需要缩进。按照 Python 的规则,在冒号后面的代码块都需要相对于冒号所在的行缩进。

当我们创建完函数之后,要实际执行函数中的代码块,还需要执行该函数。函数执行的形式比较简单:函数名之后加一个括号即可。形式如下:

函数名()

(1)小实战:打印字符三角形

现在我们用一个简单的例子来感受函数的用处。假设我们需要使用字符 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

通过上述例子可以看到,我们可以通过函数的形式给代码块起一个名字,之后只需要在代码中执行这个函数就可以起到和执行代码块一样的效果。这样的实现方式可以减少整体的代码量,也能够让代码整体更加清晰。

2、函数的参数

(1)有“变化”的变量

现在我们回过头去看开篇遇到的给三个同学的分数分级的问题。我们之前通过三个 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_out(3),发现这里是执行了一个函数,并且要把 3 传给这个函数;
  • 下一步,找到 print_out 函数的定义(第一行),把数字 3 赋值给参数列表的 a;
  • 下一步,执行函数里的代码块,也就是 print(a + 3), 此时 a 已经等于外部传入的 3,所以输出 6。

(2)使用函数参数改造分级函数

我们还是通过例子来理解,我们希望 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。

(3)小实战:打印学生信息

我们再通过一个例子加深印象。假设我们需要编写一个函数,实现打印学生的基本信息。格式如下:

姓名:小明

年龄:12

班级:九年一班

通过分析可以发现,打印前缀的提示,比如“姓名”“年龄”这些是不变的,而具体每个学生的姓名、年龄以及班级信息都是会随着学生而变化的。那显而易见,这里我们需要三个参数。

def print_info(name, age, title):

    print("姓名:" + name)

    print("年龄:" + str(age))

    print("班级:" + title)

上述代码有几个注意的点。

  • 参数的类型,name、title 是字符串类型,age 是整数类型,还记得我们在变量与数据类型一章学过,不同类型的变量做运算的时候需要先进行类型转换。这里也一样,整数和字符串不能直接拼接,需要先将整数通过 str 函数转换为字符串。
  • 我们不能直接让字符串和整型相加,所以我们通过 str 函数来将整型 age 转换为字符串再相加

执行上述代码,并新建 cell,添加下面的代码测试一下。

print_info("小明",11, "九年一班")

print_info("小红",12, "九年二班”)

输出如下:

姓名:小明

年龄:11

编制:九年一班

姓名:小红

年龄:12

编制:九年二班

可以看到我们通过两次调用 print_info 函数,并传入不同的信息,实现了打印不同学生信息的功能。 总结一下,当函数的代码需要处理每次执行都可能会变化的变量时,可以将这些变量声明为形参,放在函数名后面的括号里。然后在函数实际执行的时候,根据我们希望函数处理的变量以实参的形式传递给函数。

3、函数的返回值

通过函数的参数机制,本质上我们实现了可以向函数发送信息的本事(函数参数其实就是在函数在执行的时候,外部代码发送给函数内部代码的值)。另一方面,函数内部是否可以向外部代码发送信息呢?

从一个具体的例子来说,刚才我们打印了 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 什么?怎么拿到函数内部计算的均值?

(1)return 语句

Python 中,通过 return 关键字来实现将函数内部的值返回给调用函数的代码。形式如下:

def 函数名(参数1, 参数2, ...):

    代码块

    return 返回值

新增了以下要素。

  • return,是一个 Python 的关键字,代表将后面的值作为该函数的返回值返回给调用方。另外,return 语句一般写在最后,因为 return 除了返回值之外,还会终止当前函数的执行。类似 for 循环中的 block。简单来说,在函数体中所有在执行 return 之后的语句都不会被执行。
  • 返回值,在 return 语句后面,代表函数需要返回给外部的值,返回值可以是变量,比如上面例子的 result,也可以是一个常量,比如数字 3、字符串 “Hello” 等。

函数内部通过 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)

新版本主要有以下改动点:

  • 通过 return 语句,把 result 变量返回给外部;
  • 外部调用的时候,我们把函数返回的结果赋值给变量 final_score;
  • 打印出 final_score 的值。

输出结果为 77。

77.0

大功告成,我们的 three_average 函数在得到 return 语句的加持之后,才能变成真正意义上好用的函数。

(2)小实战:编写获取学生分级的函数

在思考一个函数该如何实现的时候,我们可以遵循以下的三段论。

  • 明确需求:就是函数具体要做一个什么事情,这就要求我们理解任务/需求的描述。
  • 明确输入:就是想清楚函数有哪些参数,扩展来说就是函数要完成上面的任务,哪些变量是需要从外部传入的。
  • 明确输出:就是函数需不需要返回值,以及返回值是什么,这同样取决于第一步,需求是什么。比如我们只是想让函数内部打印一些内容,那一般不需要返回值。而如果我们希望函数能够做一些计算,并将结果告诉调用方时,则需要返回值。

现在我们用三段论来思考一下这个实战。

  • 明确需求:把学生的分数转换为分级,并将分级返回给外部,不用打印。
  • 明确输入:学生的分数。
  • 明确输出:分级的结果。

根据上面的分析,以及 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 三个变量上。

4、Python 的库函数

关于 Python 函数的关键特性在上面已经基本介绍完毕了。Python 代码中,我们一般主要打交道的函数有两种类型:

  • 库函数,已经由 Python 的维护人员创建好的,我们可以直接使用的函数;
  • 用户自定义函数,由我们自己创建的函数。

很明显,我们在这一课中定义的 print_level、print_info 以及 three_average,list_average 函数都属于用户自定义函数。而我们一直以来使用的:

  • 打印信息倒屏幕的 print 语句;
  • 计算字符串和列表长度的 len 语句。

本质都是 Python 的库函数。

现在我们就用函数的视角,来分析一下 len 和 print 的功能

  • len 函数:接收一个参数,类型是列表或者字符串,返回值为列表和字符串的长度。
  • print 函数:接收一个或多个字符串,把他们打印在屏幕上,没有返回值。

Python 的库函数非常多,感兴趣的可以到 Python 的官方文档库浏览学习。