《从Python开始学编程》第3章 过程大于结果

记得在第一章说过,程序语言的由来就是对底层微程序的封装。可最为程序语言,我们仍然会遇到“使用重复代码”的情况,这说明“程序语言”这种语法的封装程度仍然不够。所以对于程序语言那些经常使用的代码块,我们仍然需要对其封装,恰巧所有的程序语言都提供了封装程序语言的方法,Python也不例外。之前讲过,Python是一个集成多种程序语言风格的语言,包括面向过程、面向对象等,在本章我们主要讲解在面向过程基础上对程序封装的方法,也就是马上要讲的函数。

1 函数及其定义

函数就是在面向过程的程序语言中,对程序封装的办法。在Python我们使用def关键字定义函数。def后跟函数名(自己命名,尽量通俗易懂,一般命名都采用小写字母,用下划线隔开)和参数,参数用小括号括起,在最后打上冒号即可,冒号的下面可以写函数具体内容,但都需要一个缩进。我们可以在函数内容里添加return关键字返回想要的值,注意,执行一次return后函数会立刻退出执行。

def function_name(para1, para2,...,paran):
    first content of function
    return answer1
    second content of function # 这行不会执行,在执行上一行return后会退出整个函数

我们可以把函数想象成一个黑盒,并类比数学上的函数试试。比如说这里的参数,就好比自变量,return的结果好比因变量,定义的内容好比函数里的数学式子。只要给它指定的参数,中间的过程不用操心,它会自动给出结果。第二章的勾股定理其实就是一个数学公式,也就是函数,现在我们把它转换成Python的函数试试:

def gou_gu_ding_li(bian1, bian2):
    bian3_2 = bian1**2 + bian2**2
    return bian3_2 

可以看到,我们取函数名为勾股定理,参数是两条边长度,然后函数内容是计算第三条边的平方和,并把值返回。函数写好了,那该怎么用呢,我们会在之后深入学习参数技巧,以及函数的调用。

因为Python的函数太多了,我们常常忘记某个函数时干什么的,所以Python提供帮助文档查看函数的作用。在帮助文当中,我们可以详细的看到函数的作用,每个参数的意义,以及返回值等。在Python中,我们可以直接打开帮助文档,利用Ctrl+F查找关键字来寻找帮助,也可以直接在对话窗口查看。帮助文档就不赘述了,在对话窗口查询的方式是使用关键字help。例如之前的set函数,为了得到帮助文档,我们可以这么写:

help(set)

按回车会我们会立刻看到帮助,见下图。至于英文看不懂,找百度翻译即可。

除此之外,我们自己定义的函数也可以加上帮助文档,这样通过help也可以找到。自定义函数帮助文档,仅需在函数内容开头,利用'''三单引号括起即可,但注意,一定要有缩进,看上去是函数的内容。我们在勾股定理的函数加上帮助文档:

def gou_gu_ding_li(bian1, bian2):

    '''
    函数计算并返回第三条边长度的平方,两个参数是三角形两边长
    '''

    bian3_2 = bian1**2 + bian2**2
    return bian3_2

我们可以通过以下方式查看:

help(gou_gu_ding_li)

2 函数的调用

函数有两种调用方法,一种是在一个文件内调用,另一种是跨文件调用。在一个文件内容易很多,比如我想使用勾股定理的函数,可以直接调用,返回值可以直接给一个变量。

c2 = gou_gu_ding_li(3,4)
print(c2)

如果是跨文件调用,比如我在另一个Python文件调用sump.py(这个文件所在文件夹和sump.py相同)中的勾股定理函数,那么需要先导入文件,并引用该函数。导入文件用关键字from,应用函数使用关键字import,可以这么写:

from sump import gou_gu_ding_li
c2 = gou_gu_ding_li(3,4)
print(c2)

如果sump.py文件里有许多函数想引用,可以用逗号隔开,如果想引用全部的函数,可以这么写:

from sump import *
c2 = gou_gu_ding_li(3,4)
print(c2)

其实Python有很多官方库和第三方库,都是通过这种方法调用的。其实还有一种调用方法,比如我想调用官方的math库,可以这么写:

import math

但是这么写,引用函数的写法要改变。比如我想调用pow(x,y)函数(计算x的y次方):

import math
ans = math.pow(3, 4)
print(ans)

3 作用域和参数的生命周期

从字面理解,作用域就是一段代码在整个程序运行过程有效的区域,而生命周期就是在这个作用域内执行一次的过程,一旦出了生命周期,这段代码在程序中就相当于没了,如果这时候调用这段代码,程序就会出错。

函数是有作用域的,参数是有生命周期的,其中参数又分为形参和实参。形参是在定义函数时括号里的参数,而实参就是调用时的参数。我们先讲讲形参和实参,这涉及到了函数的调用机理。记得之前我们定义了勾股定理函数,有两个形参bian1bian2,但在调用函数时,括号里却变成实实在在的数字,也就是3和4,事实上,两者是一一对应的。我们定义的形参在函数的内容里使用过,并计算出一个结果返回,那么实参里的3和4在函数调用后会分别写入bian1bian2的值里,并进行相同的计算并返回值。看看下图可以更清晰的理解。

从这个过程,我们可以清楚的看到一个问题:只有在调用函数时,bian1bian2这两个变量才有值,在return bian3_2的值给c后,这三个形参都将不复存在。于是我们可以知道这几个变量的生命周期就是在函数调用这个过程,如果在调用函数外部使用这些形参程序就会报错。

那么除了使用实参,我们真的没有其他方法沟通函数了吗?其实Python远比我们想象的强大,在函数内没有这个变量时,它可以在函数外寻找与函数内变量相名相同的变量使用。我们举个例子:

def var():
    print(var)
    var = 1

var()

这个程序最终结果会打印1,因为函数从外部找到与内部同名的var,于是就调用了。有时因为这个会出现奇怪的错误,写代码时要格外注意自己变量的调用过程。

4 参数调用技巧与函数多值调用

除了像上文的调用方法,Python还提供了多种调用方法。可以看到我们上文的gou_gu_ ding_li的参数,在调用该函数时,两个参数是一一对应的关系(可以从上图看到),这要求形参和实参顺序不能颠倒,我们现在可以这么写:

c = gou_gu_ding_li(bian2 = 4, bian1 = 3)

这样写的参数调用顺序和之前的是一模一样的。可以看到,只要分别对指定的形参赋值,传递的顺序就不会改变。我们甚至可以混着用:

c = gou_gu_ding_li(bian1 = 3, 4)

但请注意,如果混用,使用第一种方法的赋值必须和形参的位置对应,若第二种方法的赋值占用了这个位置,程序会报错。比如这么写:

c = gou_gu_ding_li(bian2 = 4, 3)

明显bian2bian1的位置占用了,实际上调用参数时没有调用bian1,所以程序会报错。

定义函数时,也有几个调用形参的方法,首先我们可以设置形参的默认值,建议参数有默认值的写在最后。

def gou_gu_ding_li(bian1, bian2 = 4):

现在我们可以这样调用函数:

c = gou_gu_ding_li(3)

因为我们没有给1传值,所以它会默认使用4这个值,和之前的调用是一样的。

有时,我们不确定需要传递多少参数,所以我们可以传入一个元组作为一个参数,而元组内可以装下任意数量的参数。参数中如果要使用元组,在该参数前加*即可,但要求这个参数必须是最后一个,所以现在我们可以这么定义gou_gu_ding_li函数:

def gou_gu_ding_li(*bian):
    bian3_2 = bian[0]**2 + bian[1]**2
    return bian3_2

那么我们现在就可以这么调用该函数:

c = gou_gu_ding_li(3,4)

程序会把“3,4”识别成元组(3,4),因此我们可以像使用元组一样使用参数。其实返回的结果也可以是元组,如果我们想返回多个值,可以在return后把返回的值用逗号隔开,那么默认返回的是元组。现在我们这么定义函数gou_gu_ding_li

def gou_gu_ding_li(*bian):
    bian3_2 = bian[0]**2 + bian[1]**2
    return bian[0], bian[1], bian3_2

当调用该函数时

c = gou_gu_ding_li(3,4)

c就是一个元组,c[2]就是bian3_2的值。

此外,我们还可以把字典所谓参数,只需要在该变量前加上两个*即可。我们可以这么定义:

def gou_gu_ding_li(**key):
    bian3_2 = key['bian1']**2+key['bian2']**2
    return bian3_2

然后我们这么调用函数:

c = gou_gu_ding_li(bian1 = 3, bian2 = 4)

参数的规则是,等号左边是键,右边是值。最终的结果和之前是一样的。其实返回值也可以是字典,我们可以这么定义:

def gou_gu_ding_li(**key):
    bian3_2 = key['bian1']**2+key['bian2']**2
    return {'bian1': key['bian1'], 'bian2':key['bian2'], 'bian3_2':bian3_2}

这样函数将返回一个字典,我们现在调用函数:

c = gou_gu_ding_li(bian1 = 3, bian2 = 4)
print(c['bian3_2'])

这个结果和之前是一样的。

所以总的来说,参数调用和函数调用非常灵活,我们可以在函数内调用函数外变量,可以以不同的顺序调用参数,可以以不同的数据结构作为参数以及函数值的返回。所以我们在使用Python函数时,要充分利用这些特性解决问题。

5 循环迭代,函数递归

迭代和递归是程序语言中最常用,最基本的算法思想,下面将举例说明。首先是迭代,以高斯求和为例,现在要求1+...+100的值,我们可以利用循环解决:

sum = 0
for i in range(1,101):
    sum = sum+i
print(sum)  # 打印5050

可以看到循环内的计算有个特点:每一次赋值都要用到之前的值。有这样特点的计算都是迭代,所以说如果以后需要用到循环来计算,第一个想到的方式应该是迭代。

下面说说递归,这算是一个难点,一般我们很难想到递归的数据流状态如何。递归其实就是在函数中继续调用函数,这个调用可以在函数内容中使用,也可以在返回值return中使用。现在可以想象一下,数据进到一个函数里,然后又进到相同的函数里,然后又进进进......似乎脑袋要炸了,这么看来它会无限循环下去,所以迭代是需要有停止条件的。现在我们把高斯求和改成递归的方式感受一下:

def guass_sum(n):  # n代表加到n,即1+...+n
    if n==1:
        return 1
    else :
        reuturn n+guass_sum(n-1)

如果我调用:

ans = guass_sum(100)

会怎样?我们来分析一下过程。首先n = 100,那么执行100+guass_sum(99)guass_sum(99)是多少呢,这需要调用这个函数才知道,于是在这个函数,n = 99, 返回99+guass_sum(98)guass_sum(98)又是多少?于是我们又要调用guass_sum(98)......就这样拼命的调用自己,直到n = 1,于是可以知道guass_sum(1) return 1,所以guass_sum(2) return 2+guass_sum(1),所以是return 2 + 1......以此方法从调用的99个自身返回,就可以知道guass_sum(100) return 100 + guass_sum(99),也就是100+99+...+1,和上面的循环一模一样,只是不那么好理解。

我们往往产生疑惑,为什么有这么简单的循环不用,用这么麻烦的递归呢?其实写起来简单的程序,用起来就不简单了,而写的不简单的反而好用一些。我们解决问题时常常要自己动手写程序,其实这个过程又叫做算法设计,我们通过设计一个计算方法,让计算机进行计算,刚刚的高斯求和就是一种算法,一般算法设计好后都会封装以便重复使用。一个好算法设计起来并不容易,于是有人就发明了一套算法的设计方法,就是分治法。分治法给我们提供一种思想设计算法,最终设计结果就是函数的递归形式。感兴趣的话可以上网查查。

有人又会说,那最终又要设计成递归,有没有更好的方式呢?其实递归等价于一种名为的数据结构,我们又可以利用栈实现递归函数的循环实现,于是递归和循环有了相互转换的桥梁。由于这涉及到算法的分析与设计,是新的一块内容,感兴趣可以上网查查,这里不多讲了。

6 异常处理

异常处理不算是函数的部分,但是可以在函数里使用。可以想象我们写程序不会一帆风顺,时不时的计算机就会送给我们一个bug(错误),如果检查错误的方式(debug)不好,会带来麻烦,浪费时间和精力。究竟是什么阻挡我们解决错误的脚步呢?

我的天,刚说完就来了个bug,这鲜红的大字,还有该死的英文,看的就烦。瞧瞧,这些就是阻碍你解决问题的绊脚石,要是你的错误可以用自己的方式表达出来就好了。Python提供异常处理机制,可以将异常以自己的方式选择输出。它的语法是这样的:

try:
    # 程序内容
except [错误名称(可以不写)] :
    # 错误时执行的代码
except :
    # 以此类推......
else:
    # 如果没有异常则执行的代码内容
finally:
    # 无论是否有异常都会执行的代码内容

错误名称由程序定义,比如上图的错误NameError就是名字错误,那么如果遇到这种错误我们想自定义输出方式,可以这么写:

try:
    print(hahahahaha)
except NameError:
    print('没有这个变量呀?!')

那么程序不会输出鲜红的大字,而是输出你想要的内容。有时这个错误里面可以写一些代码错误的补救方法。

一般一个程序有很多种类的错误,要写很多except,照顾不过来,那么索性except后不跟错误名称,无论程序遇到什么错都会跳转这里:

try:
    print(hahahahaha)
except:
    print('我不管什么错,反正你就是错了~')

不过如果你足够个性,喜欢红红的英文大字错误,也有办法。Python里可以使用raise关键字主动抛出错误,比如无法除以0的异常可以这么写:

raise ZeroDivisionError()

如果这么写,Python在执行该语句时会自动停止运行程序,并把错误给出。

实例代码可以看我的码云:第三章样例代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • 3.1 懒人炒菜机 1.函数是什么 数学上函数f(x)定义了两组数字之间的对应关系: x -> y1 12 43 ...
    泽平阿阅读 1,008评论 0 0
  • 这一章将完成面向过程的编程范式学习。 1、懒人炒菜机 1)函数是什么 函数时集合的对应关系、数据的魔法盒子、语句的...
    Guodw阅读 158评论 0 0
  • 3.1 懒人炒菜机 3.1.1 函数是什么 集合的对应关系 数据转换的魔法盒子 语句的封装。输入数据被称为参数,参...
    SibyLtuI阅读 345评论 0 0
  • 一 函数 1.1函数是什么 ①集合的对应关系 ②数据的“魔法盒子” ③语句的封装 1.2.1定义函数 制作函数的过...
    大饼与我阅读 182评论 0 1
  • 3.1 懒人炒菜机 1.何为函数 函数(Function)在数学上的定义是指集合之间的对应关系。从数据的角度,函数...
    尘濯阅读 716评论 0 3