第三章 函数

3.1 引言

如下图所示代码:

print('Howdy!')
print('Howdy!!!')
print('Hello there.')
print('Howdy!')
print('Howdy!!!')
print('Hello there.')
print('Howdy!')
print('Howdy!!!')
print('Hello there.')

经过运行之后,很简单可以得出结果。程序会输出九行代码,即print()函数引号内的内容。但是也很容易发现,这样的代码非常冗杂,十分不美观。同时,观察到这九行是以三行内容循环三遍得到的,说明可以有简单的方法。如下:

def hello():
    print('Howdy!')
    print('Howdy!!!')
    print('Hello there.')

hello()
hello()
hello()

这串代码就比上面的简单且美观很多,但是结果是否一样呢?运行如下:

通过说明,发现两者结果相当,即此种方法可以减少重复操作的复杂度,因此引出本章主题——函数。

3.2 def语句和参数

def语句就是用来定义函数。3.1所展示的代码也就运用了def函数进行定义新函数,但是定义的函数中并没有参数赋值,即hello()的里面没有赋值,只能输出规定好的内容,无法改变。但是如果在函数中加入参数,就可以随意改变了,如下:

def hello(name):
    print('Hello,'+name)

hello("AI")

与上面一个程序不同的是,hello函数中加入了name变量,也就是参数。因此,输出的部分内容就可以由参数决定。例如上述中,参数为“AI”,输出内容则为“Hello,AI”。还有一点,变量name的值为“AI”,但是在该函数运行完后,变量内的值就会自动销毁,即为函数内的定义变量(局部变量),不是整个程序的变量。

3.3 返回值和return语句

返回值,可以理解为程序运行的结果。而函数的返回值就是函数调用后的结果。return语句,就用来表示返回值。return语句包括两个部分:return关键字和函数应该返回的值或表达式。return语句的作用类似于print函数,只是print函数用于整个程序的输出,而return语句用于函数内部的结果输出。如下例:

import random

def getAnswer(answerNumber):
    if answerNumber == 1:
        return 'It is certain'
    elif answerNumber == 2:
        return 'It is decidedly so'
    elif answerNumber == 3:
        return 'Yes'
    elif answerNumber == 4:
        return 'Reply hazy try again'
    elif answerNumber == 5:
        return 'Ask again later'
    elif answerNumber == 6:
        return 'Concentrate and ask again'
    elif answerNumber == 7:
        return 'My reply is no'
    elif answerNumber == 8:
        return 'Outlook not so good'
    elif answerNumber == 9:
        return 'Very doubtful'

r = random.randint(1, 9)
fortune = getAnswer(r)
print(fortune)

如图所示,函数中加入了if语句、elif语句和return语句。首先定义了getAnswer函数,对函数中加入answerNember参数,并与if语句、elif语句结合,用于判断answerNumber参数是否符合条件,若符合就会执行子句retu——return语句。函数定义结束后,对变量r进行赋值,采用random模块进行随机取值。将r变量放入定义好的getAnswer函数中,即r等于函数定义中的answerNumber,但是因为answerNumber在函数定义完后就自动销毁了,所以需要重新定义变量,即r。最后将函数中return语句内容赋值给fortune,输出fortune,输出示例如下:

在原代码的基础上,加入了print(r),可以更直观的看到过程。如上图所示,r随机取值为1,即输出了if语句的子句内容。另外为了使代码更简单,可以对原代码的最后三行进行缩减,如下:

print(getAnswer(random.randint(1, 9)))

3.4 None值

None值本意是没有值,主要是用来区分空值和有值。None值使用的地方也比较少,因为大部分的函数都会拥有返回值,最典型的就是None值作为print()函数的返回值。因为print()函数是为了能将数据展现在屏幕上,所以他不具有返回值,但是为了区分,将None作为他的返回值。如下代码:

r = print("Hello!!")
None == r

运行结果如下:

由此看出,r和None值的比较为True,即r为None值,所以可以得出print函数的返回值就是None值。

3.5 关键词参数和print函数

本节主要介绍了print函数的两种变元,end和sep。关键字参数是指使用形式参数的名字来确定输入的参数值,而关键词参数就是用于这种“可选变元”。如下:

print("hello")
print("world")

print("hello",end='')
print("world")

这两种代码唯一的区别就是在第一个print函数中有一个end变元。而它的作用就是取消第一个print函数的自动换行。因为print函数是自带换行功能的,如果不添加end变元,输出结果就会是两行,添加之后,输出结果就是一行。

hello
world

helloworld

第二个是sep变元,如下:

print函数在输出时,默认会将内容用空格隔开,如果需要用逗号隔开的话,就需要在print函数中添加sep变元,在sep=' '的引号中添加符号,输出结果就可以用该符号将数据隔开。再示例,

3.6 栈

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。栈的使用不光在python中,在C语言中也高频使用,原理都是一样的。

例如数字1-5一次进入栈中,所以1最先进入,就是栈底,而5最后进入,就是栈顶。对于栈来说,先进后出是基本规则。5是栈顶,所以当数据出栈时,5第一个出栈,出栈结果是5-1的顺序。

以书中例子举例:

def a():
    print('a() starts')
    b()
    d()
    print('a() returns')

def b():
    print('b() starts')
    c()
    print('b() returns')

def c():
    print('c() starts')
    print('c() returns')

def d():
    print('d() starts')
    print('d() returns')
    
a()

如图所示,本程序定义了四个函数,而程序需要运行的是a函数。对于a函数,首先输出“a() starts”,下一步是b函数,所以就要调用b函数。对于b函数,首先输出“b() starts”,下一步是c函数,也要调用c函数。对于c函数,只需要输出“c() starts (\n)c() returns”。由于c函数中没有嵌套其他函数,所以返回上一级b函数继续执行,即输出“b() returns”。由于b函数也执行完毕,返回上一级a函数。下一步调用a函数中的d函数,跳转到d函数,输出“d() starts (\n)d() returns”,d函数执行完毕,返回上一级a函数,即输出“a() returns”,a函数执行完毕,运行结束。完整的输出的结果如下:

3.7 局部和全局作用域

在程序中,变量被分为局部变量和全局变量。简单来说,定义函数中的变量就是局部变量,不是全局变量,因为函数中的变量在定义结束后就销毁了,对全局来说,不存在这个变量。而独立于函数等设立的变量,可以在整个程序中调用,就是全局变量。

关于这个方面不过多叙述,简要介绍关于局部与全局变量的注意点:

1.局部变量不能再全局作用域内使用。例如,定义函数中的变量,不能再函数外使用。

2.局部作用域不能使用其他局部作用域内的变量。这一点也很好理解,因为每个局部作用域都是独立的,和其他局部作用域不一样。就算是变量名相同,但是他们不属于同一个局部作用域,所以就是不同的变量。

3.全局变量可以局部作用域中读取。

def spam():
    print(eggs)
eggs = 42
spam()
print(eggs)

如图所示,spam函数中并没有定义变量,所以当外面的全局变量eggs被赋值并调用eggs变量时,就自动将函数中的eggs认定为全局变量egss,即输出42。

4.名称相同的局部变量和全局变量是合法的。因为程序可以根据不同作用域来区分名称相同的变量,从而避免程序混乱。

3.8 global语句

global语句是用在函数内部,起到将局部变量转换为全局变量的作用,示例如下:

def spam():
    global eggs
    eggs = 'spam'

eggs = 'global'
spam()
print(eggs)

在这个代码中,eggs变量被global语句转换成全局变量,所以eggs变量被两次赋值,先被赋值为“global”,再调用spam函数,再被赋值为“spam”,所以最后的输出结果为“spam”。那如果把global语句删掉呢?

def spam():
    eggs = 'spam'

eggs = 'global'
spam()
print(eggs)

这时候函数中的eggs为局部变量,而函数外的eggs为全局变量,所以eggs在被赋值为“global”后,就没有赋值操作了。因为调用的spam函数内的eggs变量在函数执行完就自动销毁了,所以不会将值赋给全局变量eggs,那么最后的输出结果就是“global”。

另外介绍四种法则来区分变量是局部变量还是全局变量:

1.如果变量在全局作用域中使用(即在所有函数之外),它就是全局变量。

2.如果在一个函数中,有针对该变量的global语句,它就是全局变量。

3.如果该变量用于函数中的赋值语句,它就是局部变量。

4.如果该变量没有用在赋值语句中,它就是全局变量。

3.9 实践程序(动画)

import time, sys
indent = 0
indentIncreasing = True 

try:
    while True: 
        print(' ' * indent, end='')
        print('********')
        time.sleep(0.1) 

        if indentIncreasing:
            indent = indent + 1
            if indent == 20:
                indentIncreasing = False

        else:
            indent = indent - 1
            if indent == 0:
                indentIncreasing = True

except KeyboardInterrupt:
    sys.exit()

首先程序插入两个模块,time模块和sys模块。time模块是为了调用sleep函数,它的作用是让程序暂停,例如程序中的time.sleep(0.1)就是让程序暂停0.1秒,再继续。然后,定义了两个全局变量indent和indentIncreasing,并分别给两个变量赋值,初始值为0和True(布尔值)。

try和except语句是为了防止程序错误,如果运行过程中有错误,就会跳出try运行,转到except运行。对于try语句下的代码而言,运用了while循环,循环条件是True,意思是无限循环,接着运用了两个print函数,用来输出星号动画的图案。end语句让两个print函数之间不要换行,紧密连接。“*”号代表了对空格进行复制,需要复制indent次。接下来,if语句和else语句是对indentIncreasing的值进行判断,若真则执行if子句,若假则执行else子句。下面按照indent和indentIncreasing的初始值来进行分析:因为indentIncreasing的值为True,所以indent+1,即indent=0+1=1。因为indent不等于20,所以不执行变量真假转换。第一步输出的结果是0个空格+8个星号,当indent为1后,返回while,输出1个空格+8个星号。以此类推,只要indentIncreasing一直为True,indent一直加一。目前的输出结果为19个空格+8个星号,当indent=20时,indentIncreasing转变为False,然后执行20个空格+8个星号。因为indentIncreasing的值已经变为False,所以不会执行if子句,执行else子句,即indent一直减一,即空格从20依次递减为0。以此类推,当indent=0时,indentIncreasing的值再次变为True。程序如此循环无限次,即空格从0-20,再从20-0,来回循环,即可形成动画。如下视频:

20230805_193911

因为这个程序的try内容是无限循环,所以需要退出功能。对本程序而言,只要引发错误,程序就会跳转到except环节,即退出程序。而引发程序错误,即引发Keyboard-Interrupt异常,按Ctrl-C即可触发,退出程序。

3.10 小结

函数在程序设计中占据着很重要的地位,也是简化程序的重要方法之一。利用函数,可以用代码实现很多复杂的功能设计,因为函数往往和其他的知识相结合,所以掌握好函数对编程都有很深远的意义。

3.11 课后习题选讲

3.11.1

答:函数减少了重复的代码。这让程序更短,更容易阅读,更容易修改。

3.11.2

 答:有一个全局作用域;在一个函数被调用时,会创建一个局部作用域。

3.11.3

答:如果函数没有 return 语句,它的返回值就是 None

3.11.4

答:global 语句强制函数中的一个变量引用该全局变量。

3.11.5

答:该函数可以通过 spam.bacon() 调用。

3.11.6

 答:将可能导致错误的代码放在 try 子句中。将发生错误时要执行的代码放在 except 句中。

3.12 自我实践(Collatz序列)

3.12.1 基础

参考代码:

def collatz(number):
    if number%2==0 :
        return number//2
    if number%2==1 :
        return 3*number+1
print("Enter number:")
a = int(input())
while True:
    a = collatz(a)
    print(a)
    if a == 1:
        break

3.12.2 拓展

参考代码:

try:
    def collatz(number):
        if number % 2 == 0:
            return number // 2
        if number % 2 == 1:
            return 3 * number + 1

    print("Enter number:")
    a = int(input())
    while True:
        a = collatz(a)
        print(a)
        if a == 1:
            break

except ValueError:
    print("请输入一个整数!")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机懒人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值