• 【Python】第五课 函数


    5.1 什么是函数

    函数其实就是将一些需要经常执行和运用的代码进行整合打包起来,当需要用的时候直接调用即可,无需再花时间进行重新编写,这样可以加快开发项目的进度,缩短项目的开发周期。python也给我们提供了很多函数,比如之前的内建函数,也称内建方法,我们只需要调用它完成我们想要做的事情,而不需要管这个方法的内部是怎么实现的。就好比我有一堆木材,我想制作出一张书桌,但是需要工具:锤子,钉子,扳手等等,那这些工具就类似于是函数,我们直接拿来用就好,并不需要管锤子,钉子,扳手是怎么造出来的。

    理解了什么是函数之后,python给我们提供的函数是远远不够的,在我们开发项目过程中,我们大多数情况下需要自定义函数,创建我们自己符合我们功能的函数来进行使用,也就是我们自己需要自定义函数,这也就是我们要学习什么是函数的目的。

    1. # 创建函数
    2. def myFunction():
    3. print("我是函数中的执行代码")
    4. myFunction()
    5. #运行结果:我是函数中的执行代码

    以上是定义一个函数的基本结构,由def关键字开头,后面为方法名,一对小括号,然后是冒号,之后就是该方法中要执行的代码。

    5.2 带参数的函数

    5.2.1 形参和实参

    函数的作用就是用来简化我们编写程序的代码,尽量使用同一个方法来完成类似的操作。例如我需要一个加法,做整数的加法,而之后我又需要一个加法,做浮点数的加法,对于这种要求我们可以定义两个函数来完成,但是没必要,我们可以使函数带参数,完全有一个函数就能完成。

    1. # 带参数的函数
    2. #定义加法函数
    3. def add(num1,num2):
    4. print("结果为:%s"%(num1+num2))
    5. #整数相加
    6. add(10,20) #结果为:30
    7. #浮点数相加
    8. add(3.14,5.12)#结果为:8.26
    9. #定义说的函数
    10. def say(name,content):
    11. print(name+"是这样叫的:"+content)
    12. #小狗汪汪叫
    13. say("小狗","汪汪")#小狗是这样叫的:汪汪
    14. #小猫喵喵叫
    15. say("小猫","喵喵")#小猫是这样叫的:喵喵

    在我们编程语言中,把传入函数中的真实的数据称为实参(实际参数),把函数的小括号中接收数据的参数称为形参(形式参数),其实是变量的一种,我们把这种变量称为局部变量,因为这些变量只能在定义它们的方法里使用,在该方法外面是不能被使用的。

    5.2.2 函数文档

    在定义函数的时候,可以在函数中使用三重引号括起来一些重要的描述信息,以便于后期对函数调用的时候参考。

    1. # 函数文档
    2. def rate(dollar):
    3. """美元->人民币 汇率暂定为6:1"""
    4. print("兑换的金额为:%.1f元"%(dollar*6.0))
    5. rate(10)#兑换的金额为:60.0

    通过运行可以发现,函数文档是不被显示的,那么这有什么用,这个可以作为开发者便于理解该函数的注释来使用,但如果想要显示出来,那么可以通过魔术方法来将它显示出来

    1. #输出显示函数文档
    2. print(rate.__doc__)
    3. #或者使用help函数可能将该注释显示
    4. help(rate)

    5.2.3 关键字函数

    所谓关键字函数,其实是python为了方便开发者在传递参数的时候,顺序错误导致得到的结果不是预期的效果。

    1. #关键字函数
    2. def p(name,anim):
    3. print(name+"的宠物是:"+anim)
    4. #正确的输入
    5. p("我","小猫咪")#我的宠物是:小猫咪
    6. #失误的输入
    7. p("小猫咪","我")#小猫咪的宠物是:我
    8. #使用关键字函数的输入
    9. p(name="我",anim="小猫咪")#我的宠物是:小猫咪
    10. p(anim="小猫咪",name="我")#我的宠物是:小猫咪

    通过代码的运行可以发现在处理大量数据和代码的时候,我们或多或少都会出现失误,因此为了避免造成程序达不到我们预期的效果,这个关键字函数还是需要掌握的。

    5.2.4 默认参数

    所谓默认参数,其实就是在调用函数的时候,如果没有传入实参,该函数会默认调用自己准备好的备用数据作为实参进行输出。

    1. #默认参数
    2. def info(food="汉堡包"):
    3. print("我今天想吃"+food)
    4. #传入实参
    5. info("帝王蟹")#我今天想吃帝王蟹
    6. #不传实参,启用默认值
    7. info()#我今天想吃汉堡包

    5.2.5 可变参数

    可变参数其实就是不限制传入的参数个数,由调用时决定。

    1. #可变参数
    2. def test(*params):
    3. print("有 %d 个参数" % len(params))#有 6 个参数
    4. print("输入的参数为:", "".join(params))#输入的参数为: 编程使我快乐
    5. test('编', '程', '使', '我', '快', '乐')

    通过运行可发现,传入的数据一定是元组,而且不能使用小括号包裹,不然出现的效果会不同,例如

    1. #可变参数
    2. def test(*params):
    3. print("有 %d 个参数" % len(params))#有 1 个参数
    4. print("输入的参数为:", params)#输入的参数为: (('编', '程', '使', '我', '快', '乐'),)
    5. test(('编', '程', '使', '我', '快', '乐'))

    通过输出结果发现,python把一个元组当做了一个元素传入进去,最后形成了一个元组中包含了一个元组的形式。因此在打印长度的时候只会认为是一个参数。这时候需要解包:在元组或者列表前加*

    1. #可变参数
    2. def test(*params):
    3. print("有 %d 个参数" % len(params))
    4. print("输入的参数为:", params)
    5. test(*('编', '程', '使', '我', '快', '乐'))

    5.3 带返回值的函数

    python中任何函数都是有返回值的。

    1. # 函数的返回值
    2. def hello():
    3. print("hello~")
    4. print(hello())
    5. #运行结果:
    6. # hello~
    7. # None

    通过运行结果看以看出,我们在调用该函数时,确实是执行了该函数中的代码,并打印输出了结果,但第二行打印出None,我们程序中不没有编写这个内容,那么这个是怎么来的?这个是因为python中任何函数都是有返回值的,不设置return时,默认的返回值是为空,也就是return None。因此才会有第二行的内容。

    那我们自己设置返回值结果的话,再看看输出的内容:

    1. def add(x,y):
    2. return x+y
    3. print("计算的结果为:%d"%add(1,2)) #计算的结果为:3

    python中函数的返回值还能将多个结果封装成元组或者是列表进行作为返回值

    1. def test1():
    2. return [1,"abc",3.14,True]
    3. print(test1())# [1, 'abc', 3.14, True]

    在函数中的还存在变量的作用域的问题,也就是全局变量和局部变量的问题,很简单就是定义在任何方法体内部的变量称为局部变量,只能作用该方法体内部,出了该方法体是不允许被使用,而全局变量是指不定义在任何方法体内部的变量称为全局变量。

    1. #变量的作用域
    2. def discounts(price,rate):
    3. final_price=price*rate
    4. return final_price
    5. old_price=float(input("请输入原价格:"))
    6. rate=float(input("请输入折扣率:"))
    7. new_price=discounts(old_price,rate)
    8. print("打折后的价格是:",new_price)
    9. 运行结果:
    10. 请输入原价格:10
    11. 请输入折扣率:0.9
    12. 打折后的价格是: 9.0

    以上案例中,price和rate是定义在函数的小括号中的,这个我们之前已经认识了,称为形式参数,简称形参,但它也是局部变量的一样,因为该变量也是定义在函数的方法体内部,而final_price变量就是局部变量,因为它定义在该函数的方法体内,只能在该方法体内使用。old_price,rate和new_price变量属于全局变量,它们并没有包含在任何一个方法体内部。

    我们在编写程序的时候,可以试试将局部变量在函数外输出观察一下结果?

    相比全局变量是可以在任何方法体内部使用的,我们也可以试试?

    总结一下:在函数里边定义的参数以及变量,都称为局部变量,出了这个函数,这些变量都是无效的。事实上的原理是,Python在运行函数的时候,利用栈(Stack)进行存储,当执行完该函数后,函数中的所有数据都被自动删除。所以在函数外边是无法访问到函数内部的局部变量的。与局部变量相对的是全局变量,程序中old_price,rate和new_price变量属于全局变量,全局变量拥有更大的作用域。

    但如果想要在函数内部修改全局变量的值,那结果并不是和其他编程语言的结果一致,会出乎我们意料之外。

    1. def discounts(price,rate):
    2. final_price=price*rate
    3. old_price=30.0
    4. return final_price
    5. old_price=float(input("请输入原价格:"))
    6. rate=float(input("请输入折扣率:"))
    7. new_price=discounts(old_price,rate)
    8. print("打折后的价格是:",new_price)
    9. print("修改后的原价格为:",old_price)
    10. 运行结果:
    11. 请输入原价格:10
    12. 请输入折扣率:0.9
    13. 打折后的价格是: 9.0
    14. 修改后的原价格为: 10.0

    通过程序中,我们在函数体内将全局变量old_price的值修改为30.0,但打印输出的结果居然没有变化。这是因为在python中,函数体内调用全局变量,它认为是在函数体内创建了一个和全局变量名称一致的局部变量,而并非是全局变量,因此在函数外输出的old_price并不是函数体内的变量,而是外部的全局变量。

    那么该怎么做呢?这里我们需要学习一个关键字global,在函数中要使用全局变量,需要提前申请

    1. def discounts(price,rate):
    2. global old_price
    3. final_price=price*rate
    4. old_price=30.0
    5. return final_price
    6. old_price=float(input("请输入原价格:"))
    7. rate=float(input("请输入折扣率:"))
    8. new_price=discounts(old_price,rate)
    9. print("打折后的价格是:",new_price)
    10. print("修改后的原价格为:",old_price)
    11. 运行结果:
    12. 请输入原价格:10
    13. 请输入折扣率:0.9
    14. 打折后的价格是: 9.0
    15. 修改后的原价格为: 30.0

    这时候全局变量old_price的值才会发生改变。

    5.4 内嵌函数和闭包

    在python中函数是可以嵌套的,也就是在一个函数中可以定义内部函数也成为内嵌函数。

    1. #内嵌函数
    2. def fun1():
    3. print("fun1()正在被调用……")
    4. def fun2():
    5. print("fun2()正在被调用……")
    6. fun2()
    7. fun1()
    8. 运行结果:
    9. fun1()正在被调用……
    10. fun2()正在被调用……

    观察案例其实很容易发现,内嵌函数的原理和局部变量是一样的概念,定义在函数的内部的内嵌函数只能在函数的内部被调用,而不能在函数的外部调用该函数内的内嵌函数。

    那么我就想在外部调用函数的内嵌函数,那该怎么办?这我们就要学习闭包这个知识点。

    闭包(closure)是函数式编程的一个重要的语法结构,函数式编程是一种编程范式,著名的函数式编程语言就是LISP语言(主要用于绘图和人工智能,一直被认为是天才程序员使用的语言)。在python中,在一个内部函数中要使用外部函数的变量,那么内部函数就认为是闭包。

    1. #函数闭包
    2. def funX(x):
    3. def funY(y):
    4. #这里内部函数调用外部函数的x变量,因此形成闭包
    5. #其实可以理解为x相当于内部函数的全局变量,y属于内部函数的局部变量而已
    6. return x*y
    7. return funY
    8. #将8作为实参传入funX函数的x形参,
    9. # 而因为funX这个函数将内部函数作为返回值,
    10. # 因此我们可以定义一个变量来接收funX函数返回的funY内部函数
    11. fff=funX(8)
    12. #这里你可以认为fff就是funY,然后将给funY中的y形参进行赋值
    13. print("获得结果为:",fff(5))
    14. #获得结果为: 40

    看到这里,你会发现python果然很强大,再看看另外一种情况:

    通过上面的显示你会发现直接显示报错,那么是不是和前面将的全局变量一样使用global关键字先申请再使用,是的,确实是这样,只不过这时候要使用另一个nonlocal关键字来申请使用外部函数的局部变量在内部函数中使用:  

    1. def funx1():
    2. #定义一个局部变量赋值为5
    3. x=5
    4. def funy1():
    5. #申请使用外部函数x变量
    6. nonlocal x
    7. x *= x
    8. return x
    9. return funy1
    10. print("获得执行结果:",funx1()())
    11. #获得执行结果: 25

    这个原因之前已经讲过了是因为变量的值在python中是存储在栈内存中的,因此执行完后就会被删除,但如果字符串,列表,元组的方式存储数据,定义变量则不会被删除,因为字符串,列表和元组所存在的数据不是存储在栈内容中:

    1. def funx1():
    2. #定义一个局部变量赋值为5
    3. x=[5]
    4. def funy1():
    5. #取出x列表中索引值为0的值,也就是5做运算
    6. x[0] *= x[0]
    7. return x[0]
    8. return funy1
    9. print("获得执行结果:",funx1()())
    10. #获得执行结果: 25

    5.5 lambda表达式

    Python允许使用lambda关键字来创建匿名函数,也就是可以省略创建定义函数的过程。

    语法格式:

    变量=lambda 形参:执行运算代码

    1. #普通函数
    2. def ds(x):
    3. return 2*x+1
    4. print("输出结果为:",ds(5))
    5. #lambda表达式
    6. f=lambda x:2*x+1
    7. print("输出结果为:",f(5))
    8. #两个输出结果都是相同的
    9. 输出结果为: 11
    1. def add(x,y):
    2. return x+y
    3. print("计算的结果为:%d"%add(1,2))
    4. s=lambda x,y:x+y
    5. print("计算的结果为:%d"%s(1,2))

    通过以上案例发现,使用lambda表达式确实使代码更简化了,但是该方法适合用于经常需要调用并且执行代码比较精简的。

    这里介绍两个常用的内建函数filter()和map().

    filter(function or None,iterable):该方法需要两个参数,第一个参数需要传一个函数,可以允许为空,第二个参数需要传迭代对象。

    1. #过滤器函数
    2. temp=filter(None,[1,0,False,True])
    3. #默认将结果为真的值过滤筛选出来
    4. print(list(temp))
    5. 运行结果:[1, True]
    6. # 获得0-9之间的奇数
    7. def odd(x):
    8. return x%2
    9. temp=filter(odd,range(10))
    10. print(list(temp))
    11. 运行结果:[1, 3, 5, 7, 9]
    12. #使用lambda表达式的用法
    13. temp=filter(lambda x:x%2,range(10))
    14. print(list(temp))
    15. 运行结果:[1, 3, 5, 7, 9]

    map(function ,iterable):该方法需要两个参数,第一个参数需要传一个函数,第二个参数需要传迭代对象。

    map这个内建函数是对迭代的数据传入函数中进行加工获得加工后的结果

    1. temp=map(lambda x:x*2,range(10))
    2. print(list(temp))
    3. 运行结果:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

    总结:map()函数的功能包含filter()函数的作用,filter()函数只获得结果为真的值,而map()函数是获得所有计算后的结果值。

    5.6 函数递归

    递归是什么?其实就是一个函数的方法体中执行的还是调自己本身函数,这个概念上有点盗梦空间的意思,一层一层的梦,始终在梦中……,我们还是具体来看一个案例:

    使用函数递归的方式计算出n的阶乘,n!=1*2*3*4……*n

    1. #使用函数递归的方式计算出n的阶乘,n!=1*2\*3\*4……\*n
    2. #我们通过数学算得递推通式为: f(n)=n*f(n-1) ,且n=1时,f(n)=1
    3. def factorial(n):
    4. if n==1:
    5. return 1
    6. else:
    7. return n*factorial(n-1)
    8. i=int(input("请输入要计算的阶乘:"))
    9. print("计算出%d的阶乘为:"%i,factorial(i))
    10. 运行结果:
    11. 请输入要计算的阶乘:5
    12. 计算出5的阶乘为: 120

    普通程序员用迭代计算,高级程序员用递归计算,递归在各个编程语言中都可以称为递归算法,其实是数学中数列的知识转换成计算机编程语言实现的说法,称为叫递归。

    经典算法的案例:斐波那契数列

    斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从 1963 年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

    递推公式

    斐波那契数列:1,1,2,3,5,8,13,21,34,55,89...

    如果设a**n为该数列的第n项(

    ),那么这句话可以写成如下形式:

     

     显然这是一个线性递推数列

    python中使用递归演变该数列的代码如下:

    1. # 斐波那契数列
    2. #F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)
    3. def fbnq(n):
    4. if n==0:
    5. return 0
    6. elif n==1:
    7. return 1
    8. else:
    9. return fbnq(n-1)+fbnq(n-2)
    10. x=int(input("请输入斐波那契数列的第几位:"))
    11. print("计算出第%d位的值为:"%x,fbnq(x))
    12. 运行结果:
    13. 请输入斐波那契数列的第几位:9
    14. 计算出第9位的值为: 34

    经典算法的案例:汉诺塔

    法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。

    不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2f(k)+1,即通式为f(n)=2f(n-1)+1。此后不难证明f(n)=2^n-1。n=64时,

    假如每秒钟一次,共需多长时间呢?一个平年365天有31536000秒,闰年366天有31622400秒,平均每年31557600秒,计算一下:

    18446744073709551615秒

    但用python递归算法来计算,代码如下:

    1. #汉诺塔,通式为f(n)=2*f(n-1)+1 ,即f(1)=1,f(0)=0
    2. def f(n):
    3. if n==0:
    4. return 0
    5. else:
    6. return 2*f(n-1)+1
    7. x=int(input("请输入片的个数:"))
    8. print("需要移动",f(x),"次")

    运行结果:

    请输入片的个数:64 需要移动 18446744073709551615 次

    原本手算要几十年几百年的时间,现在只要几行代码,几秒钟就能得到结果,这就是编程的强大,这也是科技的进步!

  • 相关阅读:
    什么样的测试/开发程序员才是牛逼的程序员?
    图像处理Scharr 算子
    刷题1:数组篇
    Fragment使用总结
    Python中ndarray对象和list(列表)的相互转换
    修改RuoYi部署路径 适配nginx子路径访问
    typescript58-泛型类
    软件开发生命周期
    【笔者感悟】笔者的学习感悟【四】
    搭建一个自定义的工作流管理平台(二)
  • 原文地址:https://blog.csdn.net/u010321564/article/details/126577468