像计算机科学家一样思考Python —— 函数篇

举报
G-washington 发表于 2020/02/15 01:44:42 2020/02/15
【摘要】 我们通常说函数“接收”参数,并“返回”结果。这个结果称为返回值(return value)。

1 函数调用

在程序设计中,函数是指用于进行某种计算的一系列语句的有名称的组合。定义一个函数时,需要指定函数的名称并写下一系列程序语句。之后,就可以使用名称来“调用”这个函数。前面我们已经见过函数调用的例子:

image.png

这个函数的名称是 type,括号中的表达式我们称之为函数的参数。这个函数调用的结果是参数的类型。

我们通常说函数“接收”参数,并“返回”结果。这个结果称为返回值(return value)。

2 类型转换函数

Python提供了一些可将某个值从一种类型转换为另一种类型的内置函数。int函数可以把任何可以转换为整型的值转换为整型;如果转换失败,则会报错:

image.png

int可以将浮点数转换为整数,但不会做四舍五入操作,而是直接舍弃小数部分。

image.png

float函数将整数和字符串转换为浮点数:

image.png

最后,str函数将参数转换为字符串:

image.png

3 数学函数

Python有一个数学计算模块,提供了大多数常用的数学函数。模块是指包含一组相关的函数的文件。

要想使用一个模块,需要先将它导入(import)运行环境:

image.png

这个语句将会建立一个名为math的模块对象(module object)。如果打印这个对象,可以看到它的一些信息:

image.png

模块对象包含了这个模块中定义的函数和变量。若要访问其中的一个函数,需要同时指定模块名称和函数名称,用一个句点(.)分隔。这个格式称为句点表示法(dot notation)。

image.png


上面第一个例子使用了log10来计算以分贝为单位的信号/噪声比(假设signal_powernoise_power都已经事先定义好了)。math模块也提供了log函数,用来计算底为e的自然对数。

第二个例子计算radians的正弦值。这个变量名已经暗示了,sin以及costan等三角函数接受的参数是以弧度(radians)为单位的。若要将角度转换为弧度,可以除以360再乘以2π:

image.png


表达式math.pimath模块中获得变量pi。这个变量的值是π的近似值,大约精确到15位数字。

如果你了解三角函数,可以把上面的结果和2的平方根的一半进行比较:

image.png


4 组合

到现在为止,我们已经分别了解了程序的基本元素——变量、表达式和语句,但还没有接触如何将它们有机地组合起来。

程序设计语言最有用的特性之一就是可以将各种小的构建块(building block)组合起来。比如,函数的参数可以是任何类型的表达式,包括算术符号:

image.png

甚至还包括函数调用:

image.png

基本上,在任何可以使用值的地方,都可以使用任意表达式,只有一个例外:赋值表达式的左边必须是变量名称,在左边放置任何其他的表达式都是语法错误(后面我们还会看到这条规则的例外情况)。

image.png

5 添加新函数

至此,我们都只是在使用Python提供的函数,其实我们也可以自己添加新的函数。函数定义指定新函数的名称,并提供一系列程序语句。当函数被调用时,这些语句会顺序执行。

下面是一个例子:

image.png

def是关键字,表示接下来是一个函数定义。这个函数的名称是print_lyrics。函数名称的书写规则和变量名称一样:字母、数字和某些标点是合法的,但第一个字符不能是数字。关键字不能作为函数名,而且我们应尽量避免函数和变量同名。

函数名后的空括号表示它不接收任何参数。

函数定义的第一行称为函数头(header),其他部分称为函数体(body)。函数头应该以冒号结束,函数体整体缩进一级。依照惯例,缩进总是使用4个空格,参看3.14节。函数体的代码语句行数不限。

本例中print语句里的字符串使用双引号括起来。单引号和双引号的作用相同。大部分情况下,人们都使用单引号,只在本例中这样的特殊情况下才使用双引号。本例中的字符串里本身就存在单引号(单引号也作为省略符号用,如I'm)。

如果在交互模式里输入函数定义,则解释器会输出省略号(...)提示你当前的定义还没有结束:

image.png

想要结束这个函数的定义,需要输入一个空行(在脚本文件中则不需要如此)。

定义一个函数后,会创建一个同名的变量。

image.png

变量print_lyrics的值是一个函数对象,其类型是'function'

调用新创建的函数的方式,与调用内置函数是一样的:

image.png

定义好一个函数之后,就可以在其他函数中调用它。比如,若想重复上面的歌词,我们可以写一个repeat_lyrics函数:

image.png

然后可以调用repeat_lyrics

image.png


当然,这首歌其实并不是这么唱的。

6 定义和使用

将前面一节零散的代码整合起来,整个程序就像下面这个样子:

image.png


这个程序包含两个函数定义:print_lyricsrepeat_lyrics。(在解释器执行程序代码时)函数定义的执行方式和其他语句一样,不同的是执行后会创建函数对象。函数体里面的语句并不会立即执行,而是等到函数被调用时才执行。函数定义不会产生任何输出。

你可能已经猜到,必须先创建一个函数,才能执行它。换言之,函数定义必须在函数的第一次调用之前先执行。

练习1

将程序的最后一行移动到首行,于是函数调用会先于函数定义执行。运行程序并查看会有什么样的错误信息。

练习2

将函数调用那一行放回到末尾,并将函数print_lyrics的定义放到函数repeat_lyrics定义之后。这时候运行程序会发生什么?

7 执行流程

为了保证函数的定义先于其首次调用执行,你需要知道程序中语句执行的顺序,即执行流程。

执行总是从程序的第一行开始。从上到下,按顺序,每次执行一条语句。

函数定义并不会改变程序的执行流程,但应注意函数体中的语句并不立即执行,而是等到函数被调用时执行。

函数调用可以看作程序执行流程中的一个迂回路径。遇到函数调用时,并不会直接继续执行下一条语句,而是跳到函数体的第一行,继续执行完函数体的所有语句,再跳回到原来离开的地方。

这样看似简单,但马上你会发现,函数体中可以调用其他函数。当程序流程运行到一个函数之中时,可能需要执行其他函数中的语句。但当执行那个函数中的语句时,又可能再需要调用执行另一个函数的语句!

幸好Python对于它运行到哪里有很好的记录,所以每个函数执行结束后,程序都能跳回到它离开的地方。直到执行到整个程序的结尾,才会结束程序。

前面这段枯燥的描述,寓意何在?当你阅读代码时,并不总是应该一行行按照书写顺序阅读。有时候,按照执行的流程来阅读代码,可能理解效果更好。

8 形参和实参

我们已经看到,有些内置函数需要传入参数。比如,当调用math.sin时,需要传入一个数字作为实参。有的函数需要多个实参:math.pow需要两个,分别是基数(base)和指数(exponent)。

在函数内部,实参会被赋值给形参。下面的例子是一个用户自定义的函数,接收一个实参:

image.png


这个函数在调用时会把实参的值赋到形参bruce上,并将其打印两次。

这个函数对任何可以打印的值都可用。

image.png


内置函数的组合规则,在用户自定义函数上也同样可用,所以我们可以对print_twice使用任何表达式作为实参:

image.png

作为实参的表达式会在函数调用之前先执行。所以在这个例子中,表达式'Spam'*4math.cos(math.pi)都只执行一次。

你也可以使用变量作为实参:

image.png

作为实参传入到函数的变量的名称(michael)和函数定义里形参的名称(bruce)没有关系。函数内部只关心形参的值,而不用关心它在调用前叫什么名字;在print_twice函数内部,大家都叫bruce

9 变量和形参是局部的

当你在函数体内新建一个变量时,它是局部的(local),即它只存在于这个函数之内。比如:

image.png

这个函数接收两个实参,将它们拼接起来,并将结果打印两遍。下面是一个使用这一函数的例子:

image.png

cat_twice结束时,变量cat会被销毁。这时再尝试打印它的话,会得到一个异常:

image.png

形参也是局部的。比如,在print_twicebruce这个变量。

10 栈图

要跟踪哪些变量在哪些地方使用,有时候画一个栈图(stack diagram)会很方便。和状态图一样,栈图可以展示每个变量的值,不同的是它会展示每个变量所属的函数。

每个函数使用一个帧包含,帧在栈图中就是一个带着函数名称的盒子,里面有函数的参数和变量。前面的函数示例的栈图如图1所示。



图1 栈图

图中各个帧从上到下安排成一个栈,能够展示出哪个函数被哪个函数调用了。在这个例子里,printtwicecattwice调用,而cattwicemain调用。_main是用于表示整个栈图的图框的特别名称。当你在所有函数之外新建变量时,它就是属于__main的。

每个形参都指向与其对应的实参相同的值,所以,part1line1的值相同,part2line2的值相同,而brucecat的值相同。

如果调用函数的过程中发生了错误,Python会打印出函数名,以及调用它的函数的名称,以及调用这个调用者的函数名,依此类推,一直到main

比如,如果你在print_twice中访问cat变量,则会得到一个NameError:

image.png

上面这个函数列表被称为回溯(traceback)。它告诉你错误出现在哪个程序文件,哪一行,以及哪些函数正在运行。它也会显示导致错误的那一行代码。

回溯中函数的顺序和栈图中图框的顺序一致。当前正在执行的函数列在最底部。

11 有返回值函数和无返回值函数

我们使用过的函数中,有一部分函数,如数学函数,会产生结果。因为没有想到更好的名字,我称这类函数为有返回值函数(fruitful function)。另一些函数,如print_twice,会执行一个动作,但不返回任何值。我们称这类函数为无返回值函数(void function)。

当你调用一个有返回值的函数时,大部分情况下都想要对结果做某种操作。比如,你可能会想把它赋值给一个变量,或者用在一个表达式中:

image.png

在交互模式中调用函数时,Python会直接显示结果:

image.png

但是在脚本中,如果只是直接调用这类函数,那么它的返回值就会永远丢失掉!

image.png

这个脚本计算5的平方根,但由于并没有把计算结果存储到某个变量中,或显示出来,所以其实没什么实际作用。

无返回值函数可能在屏幕上显示某些东西,或者有其他的效果,但是它们没有返回值。如果你试着把它们的结果赋值给某个变量,则会得到一个特殊的值None

image.png

值None和字符串'None'并不一样。它是一个特殊的值,有自己独特的类型:

image.png

到目前为止,我们自定义的函数都是无返回值函数。

12 为什么要有函数

为什么要花功夫将程序拆分成函数呢?也许刚开始编程的时候这其中的原因并不明晰。下面这些解释都可作为参考。

  • 新建一个函数,可以让你有机会给一组语句命名,这样可以让代码更易读和更易调试。

  • 函数可以通过减少重复代码使程序更短小。后面如果你需要修改代码,也只要修改一个地方即可。

  • 将一长段程序拆分成几个函数后,可以对每一个函数单独进行调试,再将它们组装起来成为完整的产品。

  • 一个设计良好的函数,可以在很多程序中使用。书写一次,调试一次,复用无穷。

13 使用from导入模块

Python提供了两种导入模块的方式;我们已经见过其中一种:

image.png

如果你导入math,则会得到名为math的模块对象。模块对象包含了pi这样的常量以及诸如sinexp这样的函数。

但是如果直接访问pi,则会发生错误。

image.png

这时候,你可以像下面这样来导入模块中的某个对象:

image.png

现在就可以直接访问pi,而不需要使用句点表示法math.pi了。

image.png

或者,也可以使用星号来导入一个模块的所有成员:

image.png

用这种方式导入模块内所有的成员,好处是可以使你的代码更简洁,但缺点是不同模块的同名成员之间,或者和自定义的变量之间,可能发生名字冲突。

14 调试

如果你使用文本编辑器来编写脚本,则可能会遇到缩进时空格和制表符混淆的问题。避免这种问题的最好办法是只使用空格(不用制表符)。大部分识别Python的文本编辑器都默认这么处理,不过也有一些不支持。

制表符和空格都是不可见的,因而会很难调试,所以应尝试找一个能帮你处理缩进的编辑器。

另外,不要忘了在运行程序前保存它。有的开发环境会自动保存,但也有不自动保存的。如果不保存,则你写好的代码和运行的代码并不一样。

如果运行的报错的代码和你写的不一样,调试时会浪费很多时间!

所以一定要确保你眼前所看的代码和所运行的代码是一致的。如果不确定,可以在程序开头写一句print 'hello'并再运行一次。如果没有看到hello输出,则你运行的不是正确的程序!

15 术语表

函数(function):一个有名称的语句序列,可以进行某种有用的操作。函数可以接收或者不接收参数,可以返回或不返回结果。

函数定义(function definition):一个用来创建新函数的语句,指定函数的名称、参数以及它执行的语句序列。

函数对象(function object):函数定义所创建的值。函数名可以用作变量来引用一个函数对象。

函数头(header):函数定义的第一行。

函数体(body):函数定义内的语句序列。

形参(parameter):函数内使用的用来引用作为实参传入的值的名称。

函数调用(function call):执行一个函数的语句。它由函数名称和参数列表组成。

实参(argument):当函数调用时,提供给它的值。这个值会被赋值给对应的形参。

局部变量(local variable):函数内定义的变量。局部变量只能在函数体内使用。

返回值(return value):函数的结果。如果函数被当做表达式调用,返回值就是表达式的值。

有返回值函数(fruitful function):返回一个值的函数。

无返回值函数(void function):没有返回值的函数。

模块(module):一个包含相关函数以及其他定义的集合的文件。

import语句(import statement):读入一个模块文件,并创建一个模块对象的语句。

模块对象(module object):使用import语句时创建的对象,提供对模块中定义的值的访问。

句点表示法(dot notation):调用另一个模块中的函数的语法,使用模块名加上一个句点符号,再加上函数名。

组合(composition):使用一个表达式作为更大的表达式的一部分,或者使用语句作为更大的语句的一部分。

执行流程(flow of execution):程序运行中语句执行的顺序。

栈图(stack diagram):函数栈的图形表达形式,也展示它们的变量,以及这些变量引用的值。

图框(frame):栈图中的一个图框,表达一个函数调用。它包含了局部变量以及函数的参数。

回溯(traceback):当异常发生时,打印出正在执行的函数栈。

16 练习

练习3 Python提供了一个内置函数len,返回一个字符串的长度。所以len('allen')的值是5。

编写一个函数right_justify,接收一个字符串形参s,并打印出足够的前导空白,以达到最后一个字符显示在第70列上。

image.png

练习4 函数对象是一个值,你可以将它赋值给变量,或者作为实参传递。例如do_twice是一个函数,接收一个函数对象作为实参,并调用它两次:

image.png

下面是一个使用do_twice来调用一个print_spam函数两次的示例:

image.png

1.将这个示例存入脚本中并测试它。

2.修改do_twice,让它接收两个实参,一个是函数对象,另一个是一个值,它会调用函数对象两次,并传入那个值作为实参。

3.编写一个更通用的print_spam,叫做print_twice,接收一个字符串形参,并打印它两次。

4.使用修改版的do_twice来调用print_twice两次,并传入实参'spam'

5.定义一个新的函数do_four,接收一个函数对象与一个值,使用这个值作为实参调用函数4次。这个函数的函数体应该只有两个语句,而不是四个。

练习5 这个练习可以只用语句和我们已经学过的其他语言特性实现。

1.编写一个函数,绘制如下的表格:



提示

要在同一行打印多个值,你可以使用逗号分隔不同的值:

print '+','-'

如果值序列的结尾有一个逗号,Python不会换行,所以后面的打印语句会出现在同一行。

print '+',print '-'

这两个语句的输出是'+ -'。 2.编写一个函数绘制类似的表格,但有4行4列。

解答:http://thinkpython.com/code/grid.py

鸣谢:这个练习基于Oualline的《实践C编程》第3版(O'Reilly Media,1997)中的一个示例。

 这一段中讲的参数有两种:函数定义里的形参(parameter),以及调用函数时传入的实参(argument),这里两种是有区分的。——译者注


本文节选自《像计算机科学家一样思考Python》



内容简介


本书按照培养读者像计算机科学家一样的思维方式的思路来教授Python语言编程。全书贯穿的主体是如何思考、设计、开发的方法,而具体的编程语言,只是提供一个具体场景方便介绍的媒介。它并不是一本介绍语言的书,而是一本介绍编程思想的书。和其他编程设计语言书籍不同,它不拘泥于语言细节,而是尝试从初学者的角度出发,用生动的示例和丰富的练习来引导读者渐入佳境。

作者从最基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量、表达式、语句、函数和数据结构。此外,书中还探讨了如何处理文件和数据库,如何理解对象、方法和面向对象编程,如何使用调试技巧来修正语法、运行时和语义错误。每一章都配有术语表和练习题,方便读者巩固所学的知识和技巧。此外,每一章都抽出一节来讲解如何调试程序。作者针对每章中所专注的语言特性,或者相关的开发问题,总结了调试的方方面面。可以说这是一种非常有益的创新,让初学编程的读者少走很多弯路。

全书共19章和3个附录,详细介绍了Python语言编程的方方面面。这是一本实用的学习指南,适合没有Python编程经验的程序员阅读,也适合高中或大学的学生、Python爱好者及需要了解编程基础的人阅读。对于第一次接触程序设计的人来说,是一本不可多得的佳作。

本文转载自异步社区

原文链接:https://www.epubit.com/articleDetails?id=NC7E3EF91548000018FAE13C312F953F0

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。