像计算机科学家一样思考python-第3章 函数

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

3.1函数调用

一个函数调用的例子

>>> type(42)

<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 * 2 * math.pi)

甚至还包括函数调用:

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

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

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 =1

>>> 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(): #1
print_lyrics()#4
print_lyrics()#7
def print_lyrics():#2
print("I‘m a lumberjack,and I‘m okay")#5  #8
print("I sleep all night and I Work all day.")#6  #9
repeat_lyrics()#3

为什么练习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‘*4)
span span span span
span span span span 

print_twice(42)
42
42

print_twice(math.pi)

3.141592653589793

3.141592653589793

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

>>> print_twice(‘span ‘*4)

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 1, in <module>
  File "<stdin>", line 3, in cat_twice
  File "<stdin>", line 4, in print_twice
NameError: name ‘cat‘ is not defined

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

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

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

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

>>> a=2

>>> b=2

>>> a is b

True

>>> id(a)

10943040

>>> id(b)

10943040

>>> a=2.3

>>> b=2.3

>>> a is b

False

>>> id(a)

139801075219456

>>> id(b)

139801075219648

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

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

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

x = math.cos(radians)

golden = (math.sqrt(5)+1)/2

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

>>> math.sqrt(5)

2.23606797749979

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

math.sqrt(5)

这个脚本计算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):当异常发生时,打印出正在执行的函数栈。

原文地址:https://www.cnblogs.com/kaerxifa/p/11372937.html

时间: 2024-10-07 23:05:01

像计算机科学家一样思考python-第3章 函数的相关文章

像计算机科学家一样思考Python (第2版)高清PDF电子版下载

本书以培养读者以计算机科学家一样的思维方式来理解Python语言编程.贯穿全书的主体是如何思考.设计.开发的方法,而具体的编程语言,只是提供了一个具体场景方便介绍的媒介. 全书共21章,详细介绍Python语言编程的方方面面.本书从基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量.表达式.语句.函数和数据结构.书中还探讨了如何处理文件和数据库,如何理解对象.方法和面向对象编程,如何使用调试技巧来修正语法错误.运行时错误和语义错误.每一章都配

像计算机科学家一样思考Python (第2版)pdf

下载地址:网盘下载 内容简介  · · · · · · 本书以培养读者以计算机科学家一样的思维方式来理解Python语言编程.贯穿全书的主体是如何思考.设计.开发的方法,而具体的编程语言,只是提供了一个具体场景方便介绍的媒介. 全书共21章,详细介绍Python语言编程的方方面面.本书从基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量.表达式.语句.函数和数据结构.书中还探讨了如何处理文件和数据库,如何理解对象.方法和面向对象编程,如何使用

像计算机科学家一样思考python笔记

[TOC] #1 程序之道 ##1.1 Python程序语言 - 有两种程序可以让高级语言转换为低级语言,分别是直译器和编译器. - 有两种方式使用python的直译器:shell模式和脚本模式. ##1.8 形式语言和自然语言 - 自然语言是人们所说的语言. - 形式语言是人们为特定的应用设计的语言 *<b>程序语言是设计来呈现计算的形式语言</b>* 语法规则分为两种,分别属于标记和结构.标记是程序语言的基本组件,就像字词.数字和化学元素一样; 结构是标记的排列方法. >

读《像计算机科学家一样思考python》&mdash;&mdash;笔记(2/2)

包括如下目录: 第十四章 文件 第十五章 类和对象 第十六章 类和函数 第十七章 类和方法 第18章 继承 第十四章 文件 1. 读和写 一个文件需要先打开,然后再进行读与写, 最后把打开的文件关闭. #读文件 >>> f = open('words.txt') >>> print f <open file 'words.txt', mode 'r' at 0x7fd74378e4b0> >>> f.readline() 'aa\r\n'

python第五章函数

第五章 函数 5.1三元运算/三目运算 v = 前面 if 条件语句 else 后面 #如果条件成立,"前面"赋值给v,否则后面赋值给v. v = a if a>b else b # 取a和b中值较大的赋值给v # 让用户输入值,如果值是整数,则转换成整数,否则赋值为None data = input('请输入值:') value = int(data) if data.isdecimal() else None 5.2 函数 5.2.1.函数介绍 截止目前为止,都是面向过程式编

《像计算机科学家一样思考Java》—— 读后总结

本书属于入门级的Java书籍,与其他的向编程思想.核心技术不同的是,这本书不是按部就班的讲解java变成知识,而是随着语言的深入慢慢增加知识点. 这本书以一个语言开发者的角度,深入浅出的讲解了Java语言的机制. 比如语言最基本的变量和方法,到后续的深入,功能的增加,逐渐的加大难度与知识点. 本书内容 程序语言 一门编程语言,在学习之前要看它是高级语言.还是低级语言.低级语言更接近计算机底层,但是不容易编写和理解,比如汇编.还要看它是解释型的还是需要编译的.比如html,css都是属于解释型的,

python 第四章 函数

函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可以自己创建函数,这被叫做用户自定义函数. 定义一个函数 你可以定义一个由自己想要功能的函数,以下是简单的规则: 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 (). 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数. 函数的第一行语句可以选择性地使用文档字符串-用于存放函数说明

Python笔记&#183;第九章—— 函数 (一)

一.函数的作用 函数可以让我们代码结构更清晰,而且避免了代码的重复,冗余,使一段代码或者功能可以反复的被调用,大大提高了开发效率 二.函数的定义 def 函数名(参数1,参数2,*args,默认参数,**kwargs): """注释:函数功能和参数说明""" 函数体 -- return 返回值 1.定义:def 关键词开头,空格之后接函数名称和圆括号(). 2.参数:圆括号用来接收参数.若传入多个参数,参数之间用逗号分割. 参数可以定义多个,也可

总结数据科学家常用的Python库

概述 这篇文章中,我们挑选了24个用于数据科学的Python库. 这些库有着不同的数据科学功能,例如数据收集,数据清理,数据探索,建模等,接下来我们会分类介绍. 您觉得我们还应该包含哪些Python库?让我们知道! 介绍 我是Python语言的忠实粉丝,它是我在数据科学方面学到的第一门编程语言.Python有三个特点: 它的易用性和灵活性 全行业的接受度:它是业内最流行的数据科学语言 用于数据科学的庞大数量的Python库 事实上,有如此多的Python库,要跟上它们的发展速度可能会变得非常困难