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

3.1函数调用

一个函数调用的例子

>>> type()

<class 'int'>

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

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

3.2数学函数

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

要想使用模块中的函数,需要先使用import语句将它导入运行环境

>>> import math

这个语句将会创建一个名为math的模块对象。如果显示这个对象,可以看到它的一些信息:

>>> math

<module 'math' (built-in)>

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

>>> radians=0.7

>>> height=math.sin(radians)

>>> height

0.644217687237691

3.3组合

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

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

>>> x = math.sin(degrees / 360.0 *  * math.pi)

甚至还包括函数调用:

>>> math.exp(math.log(x+))

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

3.4添加新函数

我们可以自己添加新的函数。函数定义指定新函数的名称,并提供一系列程序语句,当函数被调用时,这些语句会顺序运行。

下面是一个例子:

>>> def print_lyrics():

... print("I'm a lumberjack,and I'm okay")

... print("I sleep all night and I Work all day.")

def是一个关键字,表示接下来是一个函数定义。这个函数的名称是print_lyrics。函数名称的书写规则与变量名称一:字母、数字、下划线是合法的,但第一个字符不能是数字。关键字不能作为函数名,而且我们应用尽量避免函数和变量同名。函数名后的空括号表示它不接收任何参数。

有所思:如果函数名与变量名一样会发生什么情况呢?

假如有一个变量a=1,后面又定一个函数也叫a,执行1个print语句,这时候变量名a指向的是函数对象呢,还是int类型的1呢?我猜肯定指向的是函数a,因为python是按顺序执行的语句,如果同一个变量名被重复定义,则以最后一次定义的内容为准。

>>> a =

>>> type(a)

<class 'int'>

>>> def a():

... print("函数a")

...

>>> type(a)

<class 'function'>

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

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

代码中所有的引号(包括双引号和单引号)都必须是”直引号”,通常在键盘上Enter键附近。而”斜引号”,在Python中是非法的。

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

>>> def print_lyrics():

... print("I'm a lumberjack,and I'm okay")

... print("I sleep all night and I Work all day.")

... 

想要结束这个函数的定义,需要输入一个空行。

定义一个函数会创建一个函数对象,其类型是”function”。

>>> print(print_lyrics)

<function print_lyrics at 0x7f25f96a8e18>

>>> type(print_lyrics)

<class 'function'>

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

>>> print_lyrics()

I'm a lumberjack,and I'm okay

I sleep all night and I Work all day.

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

>>> def repeat_lyrics():

... print_lyrics()

... print_lyrics()

然后调用
repeat_lyrics:

>>> repeat_lyrics()

I'm a lumberjack,and I'm okay

I sleep all night and I Work all day.

I'm a lumberjack,and I'm okay

I sleep all night and I Work all day.

3.5 定义和使用

将前面一节的代码片段整合起来,整个流程就像下面这个样子:

def print_lyrics():
print("I'm a lumberjack,and I'm okay")
print("I sleep all night and I Work all day.") def repeat_lyrics():
print_lyrics()
print_lyrics() repeat_lyrics()

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

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

练习1:

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


repeat_lyrics()

def print_lyrics():
print("I'm a lumberjack,and I'm okay")
print("I sleep all night and I Work all day.") def repeat_lyrics():
print_lyrics()
print_lyrics()

运行程序报错如下:

NameError: name 'repeat_lyrics' is not defined

练习2:

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


def repeat_lyrics():
print_lyrics()
print_lyrics()
def print_lyrics():
print("I'm a lumberjack,and I'm okay")
print("I sleep all night and I Work all day.")
repeat_lyrics()

我以为会报错,提示 print_lyrics 函数没有定义,为什么没有报错呢?

打开程序的debug模式调试程序,发现程序的执行顺序是这样的


def repeat_lyrics(): #
print_lyrics()#
print_lyrics()#
def print_lyrics():#
print("I'm a lumberjack,and I'm okay")# #
print("I sleep all night and I Work all day.")# #
repeat_lyrics()#

为什么练习1会报错,而练习2却能正常运行呢?

原因就是:函数定义必须在函数被调用之前先运行
函数体里面的语句并不会立即运行,而是等到函数被调用时才执行

在练习2中,在repeat_lyrics函数的函数体中,调用
print_lyrics函数,而在执行repeat_lyrics函数前(#3),
print_lyrics已经被定义过了(#2),所以程序不会出错。

而在练习1中,程序自上而下运行,首先执行调用repeat_lyrics函数的语句,而在此条语句之前又没有执行定义repeat_lyrics函数的语句所以会出错。

举一反三,下面这样执行程序也会报错,因为
print_lyrics函数还没有被定义,就已经被调用。

def repeat_lyrics():
print_lyrics()
print_lyrics() repeat_lyrics() def print_lyrics():
print("I'm a lumberjack,and I'm okay")
print("I sleep all night and I Work all day.") NameError: name 'print_lyrics' is not defined

3.6 执行流程

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

执行总是从程序的第一行开始。语句按照从下到下的顺序逐一运行。

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

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

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

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

总之,在阅读代码时,并不总应该按照代码书写顺序一行行阅读;有时候,按照程序执行的流程来阅读代码,理解的效果可能会更好。

3.7
形参和实参

在函数内部,实参会赋值给称为形参(parameter)的变量。下面的例子是一个函数的定义,接收一个实参


def print_twice(bruce):
print(bruce)
print(bruce)

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

print_twice('span'*)
span span span span
span span span span print_twice() print_twice(math.pi) 3.141592653589793 3.141592653589793

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

>>> print_twice('span '*)

span span span span

span span span span

>>> print_twice(math.cos(math.pi))

-1.0

-1.0

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

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

>>> michael = 'Eric,the half a bee'

>>> print_twice(michael)

Eric,the half a bee

Eric,the half a bee

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

3.8
变量和形参是局部的

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

def print_twice(bruce):
print(bruce)
print(bruce) def cat_twice(part1,part2):
cat = part1+part2
print_twice(cat)
这个函数接收两个实参,将它们拼接起来,并将结果打印两遍。下面是一个使用这一函数的例子:
line1 = 'Bing tiddle '
line2 = 'tiddle bang. ' >>> cat_twice(line1,line2) Bing tiddle tiddle bang. Bing tiddle tiddle bang.


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

>>> print(cat)

NameError: name 'cat' is not defined

形参也是局部的。例如,在
print_twice函数之外,不存在
bruce这个变量。

3.9
栈图

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

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

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

每个形参都指向与其对应的实参相同的值,所以part1和line1的值相同,part2和line2的值相同,而bruce和cat的值相同。

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

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


Traceback (most recent call last):
File "<stdin>", line , in <module>
File "<stdin>", line , in cat_twice
File "<stdin>", line , in print_twice
NameError: name 'cat' is not defined

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

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

这里插个题外话,说到形参与实参的值相同,突然想到一个点,如果a与b的值相同,a

b可以视作是同个对象吗?神奇的是如果a与b都指同一个整数(指向同一个对象地址),python会认为它们是同一个对象,而如果a与b是浮点型,则python不认为它们是同一个对象(指向不同的对象地址)

>>> a=

>>> b=

>>> a is b

True

>>> id(a)

>>> id(b)

>>> a=2.3

>>> b=2.3

>>> a is b

False

>>> id(a)

>>> id(b)

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

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

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

x = math.cos(radians)

golden = (math.sqrt()+)/

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

>>> math.sqrt()

2.23606797749979

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

math.sqrt()

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

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

>>> result = print_twice('Bing')

Bing

Bing

>>> print(result)

None

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

>>> type(None)

<class 'NoneType'>

到目前为止,我们自定义的函数都是无返回值函数。再过几章我们就会开始写有返回值的函数了。

3.11 为什么要有函数

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

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

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

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

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

3.12调试

你将会掌握的一个最重要的技能就是调试。虽然调试可能时有烦恼,但它的确是编程活动中最耗脑力、最有挑战、最有趣的部分。

在某种程度上,调试和刑侦工作很像。你会面对一些线索,而且必须推导出事情发生的过程,以及导致现场结果的事件。

调试也像是一种实验科学。一旦猜出错误的可能原因,就可以修改程序,再运行一次。如果猜对了,那么程序的运行结果会符合预测,这样就离正确的程序更近了一步。如果猜错了,则需要重新思考。正如夏洛克.福尔摩丝所说的:“当你排除掉所有的可能性,那么剩下的,不管多么不可能,必定是真相。”

对某些人来说,编程和调试是同一件事。也就是说,编程正是不断调试修改直到程序达到设计目的的过程。这种想法的要旨是,应该从一个能做某些事的程序开始,然后做一点点修改,并调试改正,如此迭代,以确保总是有一个可以运行的程序。

例如,Linux是包含了数百万行代码的操作系统,但最开始只是Linus
Torvalds编写的用来研究Intel
80386芯片的简单程序。据Larry
Greenfield所说:“Linus最早的一个程序是交替打印AAAA

BBBB。后来这些程序演化成了Linux”

3.13术语表

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

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

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

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

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

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

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

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

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

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

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

无返回值函数(void
function):总是返回None的函数

None:由无返回值函数返回的一个特殊值。

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

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

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

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

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

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

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

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

最新文章

  1. 基于ListBox的相关操作
  2. Part 1: Running Oracle E-Business Suite on Oracle Cloud
  3. 在Main中定义student的结构体,进行年龄从大到小依次排序录入学生信息。(结构体的用法以及冒泡排序)
  4. lucene搜索方式(query类型)
  5. DATAGUARD中手工处理日志v$archive_GAP的方法
  6. [JIT_APP]Java基础知识总结
  7. 郝斌老师C语言学习笔记(一)
  8. 删除字符串中多余的空白字符和空行(C语言实现)
  9. Web安全測试二步走
  10. 简单实现TCP下的大文件高效传输
  11. 安装lamp代码
  12. iOS HTML图片本地预览
  13. (NO.00001)iOS游戏SpeedBoy Lite成形记(十二)
  14. $(function(){})简述
  15. 一文掌握 Linux 性能分析之内存篇
  16. [原创]Struts2奇葩环境任意文件上传工具(解决菜刀无法传文件或上传乱码等问题)
  17. 开启spark日志聚集功能
  18. java学习第05天(数组常见操作、数组中的数组)
  19. bitcoin双花
  20. Codeforces 260D - Black and White Tree

热门文章

  1. go 关键字之 defer
  2. P2P技术
  3. https://www.cnblogs.com/cncc/p/7804511.html?foxhandler=RssReadRenderProcessHandler
  4. iOS 审核app被拒绝的各种理由以及翻译
  5. MacOS X快捷键一览(http://www.cnblogs.com/ios8/p/Mac-OSX-keyword-cmd.html)
  6. mv - 移动 (改名) 文件
  7. python小实例
  8. Fokker–Planck equation
  9. python List 常用方法
  10. Python 基本数据类型详解