当前位置: 首页 > 后端技术 > Python

【Python从入门到精通】(12)Python函数进阶知识点,知识吸收更深入【收藏保证有用!!!】

时间:2023-03-26 13:42:04 Python

大家好,我是飞哥,感谢您阅读本文,欢迎点击三连。本文将介绍Python函数的进阶知识点:重点介绍函数参数传递机制和函数式编程。干货满满,建议收藏,需要时不时看看。如果您有任何疑问或需求,欢迎随时留言~~~。前言上一篇其实介绍了Python函数,不过都是函数的基础知识点。没看过的,抽空看看【Python从入门到精通】(11)方方面面的Python函数【收藏起来,保证有用!!!】.本文将重点深入介绍函数参数传递机制、lambda表达式和函数式编程等知识点。从入门到精通,不能总有一些简单的知识,还要有一些硬货。深入知识的海洋。Python函数参数传递机制在上一篇文章中,我们提到了Python函数参数传递机制有两种:值传递和引用传递。那么这两种方法有什么区别呢?具体的参数传递机制是什么?本章将在以后回答这两个问题。我们先来看值传递。以下代码定义了一个带有两个输入参数a和b的交换函数。这个函数的工作是交换输入参数a和b的值。defswap(a,b):a,b=b,aprint("形参a=",a,'b=',b)returna,ba,b='麦农飞哥','加油'print("a=",a,'b=',b)函数调用前实参swap(a,b)print("a=",a,'b=',b调用后实参函数)运行结果为:a=调用函数前的实参,b=加油,形参a=加油b=,实参a=农夫飞哥代码b=调用后加油函数中,可以看到形参修改成功,但不影响实参。为什么?这其实是因为swap函数中形参a和b的值是实参a和b的拷贝,也就是说调用swap后,python会将输入参数a和b拷贝到形参中交换函数的参数。对副本的更改当然不会影响原始值。语言的描述是空的,我们画个图来说明一下:在Python中,一个方法对应一个栈帧,栈是后进先出的结构。上面提到的过程可以用下面的调用图来表示:可以看出,在执行代码a,b='代码农飞哥','Comeon'时,Python会初始化a和b中的值主函数栈。调用swap函数时,将main函数中a和b的值复制,传递给swap函数栈。swap函数交换a和b的值时,只影响a和b的副本,对a和b本身没有影响。但是对于list和dictionary这样的数据类型,由于数据存放在堆中,栈中存放的只是引用,所以当形参数据被修改时,实参也会发生变化。.下面代码演示:defswap(dw):#下面代码实现dw的a和b元素的值交换dw['a'],dw['b']=dw['b'],dw['a']print("在swap函数中,a=",dw['a'],"b=",dw['b'])dw={'a':'麦农飞哥','b':'Comeon'}print("调用函数前在外部dw字典中,a=",dw['a'],"b=",dw['b'])swap(dw)print("External调用函数后的dw字典中,a=",dw['a'],"b=",dw['b'])运行结果为:在调用函数前的外部dw字典中,a=码农飞哥b=swap函数中加油a=加油b=码农飞哥调用函数后在外部dw字典中,a=加油b=码农飞哥可以清楚的看到实参dw的值调用函数后传入的确实发生了变化。这说明他是引用传递,那么引用传递和值传递有什么区别呢?从上图可以看出字典的数据是存放在堆中的,在main函数的栈中通过引用指向存放字典的内存区域。调用swap函数时,python会把dw的引用复制到形参中,当然复制的引用指向存放同一个字典的内存区。当通过复制引用操作字典时,字典的数据当然也发生了变化。总结一下:引用传递本质上就是值传递,只是值指的是引用指针本身,而不是引用指向的值。为了验证这个结论,我们可以稍微修改一下上面的代码:defswap(dw):#下面的代码实现了dw的a和b元素的值交换dw['a'],dw['b']=dw['b'],dw['a']print("交换函数中,a=",dw['a'],"b=",dw['b'])dw=Noneprint("删除参数A对字典的引用",dw)dw={'a':'飞哥的代码','b':'加油'}print("调用函数前在外部dw字典中,a=",dw['a'],"b=",dw['b'])swap(dw)print("调用函数后在外部dw字典中,a=",dw['a'],"b=",dw['b'])运行结果为:调用函数前在外部dw字典中,a=码农飞哥b=加油在swap函数中,a=加油b=码农飞哥删除了从形参中引用字典和调用函数后的外部dw字典其中,a=Comeonb=CoderFeige删除了从形参中引用字典,但是实际参数仍然可以得到字典的值。这充分说明传递给形参的是实参的引用副本。递归函数相信很多朋友对递归函数都不陌生。递归函数是在函数内部调用自身的函数。递归函数常用于序列计算、遍历省市、文件夹等场景。需要注意的是,如果满足某个条件,递归函数必须能够不再调用自身。否则,可能会不断调用,陷入死循环。例如:现在有一个序列:f(0)=1,f(1)=4,f(n+2)=2*f(n+1)+f(n),其中n是一个大于的整数0,求f(5)的值。这个问题可以用递归来解决。下面的程序将定义一个fn()函数来计算f(5)的值。根据f(n+2)=2*f(n+1)+f(n)公式,可推导出f(n)=2*f(n-1)+f(n-2)公式。deffn(n):ifn==0:return1ifn==1:return4return2*fn(n-1)+fn(n-2)print(fn(5))结果是128,我们来推导函数执行过程:当n=5时,函数返回2*fn(4)+f(3)。当n=4时,函数返回2*fn(3)+f(2)。当n=3时,函数返回2*fn(2)+f(1)。当n=2时,函数返回2*fn(1)+f(0)当n=1时,函数返回4n=0时,函数返回1。所以,fn(2)=9,f(3)=22、f(4)=53、f(5)=128。他的调用过程是逐层递归,直到得到内层的结果。然后反转得到外层的结果。变量作用域变量的作用域->说白了就是变量可以作用的范围。Python中有两种类型的变量:局部变量和全局变量。定义在函数内部的变量称为局部变量,它的作用域在函数内部,与函数同生同死。就像一线员工一样,你的权限范围在自己的部门(职能)之内,部门之外的事情你是管不了的。局部变量的初始化过程是:当函数执行时,Python会为其分配一个临时存储空间,函数内部定义的所有变量都会存储在这个空间中。函数执行完毕后,这个暂存空间被释放回收。自然地,存储在这个空间中的变量就不能再使用了。正如上面代码中的obj变量和name变量在函数内部可以正常使用一样,会提示NameError:name'obj'isnotdefinedoutsidethefunction。所以可以得出结论,局部变量不能在函数外使用,形参变量也是局部变量。在函数外部定义的变量称为全局变量,其作用域是在整个应用程序中,即全局变量可以在每个函数外部使用,也可以在每个函数内部使用。正如老板可以对整个公司(整个应用)发号施令,那么各个小部门(职能部门)也必须听他的。就像下面代码中的name变量一样,它可以在函数param_test1的内部和外部使用。name='麦农飞哥'defparam_test1(obj):print('name=',name)print('name=',name)获取指定范围内的变量,使用global()函数获取指定范围内的变量python文件全局变量,其结果是一个字典。函数中的局部变量可以通过在函数内部调用locals()函数来获取。举个例子!defparam_test1(obj):name='张三'print('局部变量=',locals())returnobj+nameparam_test1('李四')name2='张三'print('全局变量有',globals())运行结果为:局部变量have={'name':'张三','obj':'李四'}全局变量have{'__name__':'__main__','__doc__':'@日期:2021/7/1921:23\n@desc:\n','__package__':无,'__loader__':<_frozen_importlib_external.SourceFileLoader对象位于0x00000226B627B208>,'__spec__':无,'__annotations__':{},'__builtins__':,'__file__':'/python_demo_1/demo/function/param_test_fun.py','__cached__':None,'param_test1':,'name2':'张三'}如何在函数中使用同名的全局变量当函数内部的局部变量与函数外部的全局变量重名时,局部变量会“遮蔽”函数内同名的全局变量。正所谓猛龙不压地头蛇,函数内部的局部变量就是局部变量,全局变量猛龙也会被它打压。name="张三"deftest_1():#访问全局变量print(name)name="李四"test_1()运行这个函数会报如下错误:Traceback(mostrecentcalllast):File"python_demo_1/demo/function/param_test_fun.py”,第33行,在test_1()文件“/python_demo_1/demo/function/param_test_fun.py”,第31行,在test_1print(name)UnboundLocalError:localvariable'name'referencedbeforeassignment上面的错误信息告诉我们在执行print(name)的时候还没有定义name,因为name是在第五行定义的。其实我们期望在第四行打印全局变量名的值,但是由于在函数的第五行定义了一个同名的局部变量名(Python语法规定在给变量赋值时函数内部不存在的,默认是重新定义一个新的局部变量)。局部变量名称“遮盖”了全局变量名称。同时,局部变量name在print(name)之后进行初始化,违反了“先定义后使用”的原则,因此程序会报错。如何防止“影子”的情况?那么如何防止全局变量被函数内部的同名局部变量“遮蔽”呢?这里有两种方式:直接访问被遮蔽的全局变量,如果希望程序仍然访问name全局变量,并且可以在函数中重新定义name局部变量,可以使用globals()函数来实现。name="张三"deftest_1():name="李四"print('局部变量name=',name)#访问全局变量print('全局变量name=',globals()['name'])test_1()运行结果为:局部变量名=李四全局变量名=张三直接通过globals()['name']访问全局变量名,达到与局部变量名相同的效果。全局变量通过global关键字在函数中声明。为了避免在函数中给全局变量赋值(不是重新定义局部变量),可以使用global语句来声明全局变量。name="码农飞哥"deftest_2():globalname#访问全局变量print('全局变量name=',name)name="小薇薇"print('局部变量name=',name)print('Globalvariablename=',globals()['name'])test_2()运行结果为:全局变量名=码农飞哥局部变量名=小薇薇全局变量名=小薇薇使用同一个全局关键字同名全局变量可以被局部变量覆盖。global修饰全局变量后,在定义同名局部变量之前使用全局变量。函数的高级用法函数赋值给其他变量函数不仅可以直接调用,还可以直接将函数赋值给其他变量。就像下面定义了一个名为my_fun的函数,首先将函数名my_fun赋值给变量other,然后通过other间接调用my_fun()函数。defmy_fun():print("my_funfunctionisbeingexecuted")#将函数赋值给其他变量other=my_fun#间接调用my_fun()函数other()运行结果为:my_fun函数正在执行,函数作为参数传递给其他函数Python也支持将函数作为参数传递给其他函数,也就是说函数可以作为形参传递。举个栗子!defmy_fun1():return'加油,一定要做'defmy_fun2(name,my_fun1):returnname+my_fun1print(my_fun2('麦农飞哥',my_fun1()))运行结果是码农飞哥,加油,你确定。上面的代码首先定义了一个函数my_fun1,然后将函数my_fun1作为参数传递给了函数my_fun2。该代码工作正常。局部函数(内部函数)及用法Python不仅支持在函数内部定义局部变量,也支持在函数内部定义函数。这样的函数称为局部函数。举个例子!#全局函数defouter_fun():definner_fun():print('Calllocalfunction')returninner_fun#Calllocalfunctionnew_inner_fun=outer_fun()new_inner_fun()上面的代码先定义了全局函数outer_fun,然后在里面定义该函数创建了一个本地函数inner_fun。然后返回本地函数。根据前面函数可以赋值给变量的知识,可以成功调用局部函数inner_fun。需要注意的是,如果一个局部函数定义了一个与函数中的变量同名的变量,也会出现“阴影”的问题。避免这个问题的方法不再是使用global关键字,而是使用nonlocal关键字。就像下面!#全局函数defouter_fun():name='加农飞哥'definner_fun():print('调用局部函数')nonlocalnameprint('globalvariablename=',name)name='小薇薇'print('Localvariablename=',name)returninner_fun#Callthelocalfunctionnew_inner_fun=outer_fun()new_inner_fun()运行结果为:调用局部函数globalvariablename=码农飞哥localvariablename=小薇薇lambda表达式Python是支持的拉姆达表达式。Lambda表达式,也称为匿名函数,通常用于表示内部包含1行表达式的函数。其语法结构为:name=lambda[list]:expression其中,定义一个lambda表达式,必须使用lambda关键字;[list]为可选参数,相当于将函数定义为一个指定的参数列表;name是表达式Thename。比如现在有如下add函数,入参为x,y。回报是x+y。defadd(x,y):returnx+y这个函数可以改写为lambda表达式,结果为lambdax,y:x+y。然后将其分配给一个添加变量。如下图。add=lambdax,y:x+yprint(add(2,3))当然lambda表达式远不止这个技巧,结合接下来介绍的函数式编程会产生不一样的效果。接下来,让我们看看函数式编程。在函数式编程中,当普通函数的输入参数是列表或字典时,当形参被修改时,实参也会发生变化。像这样:test_list=[1,2,3,4]defsecond_multi(test_list):foriinrange(len(test_list)):test_list[i]=test_list[i]*2returntest_listforiinrange(3):print('第{0}次运行的结果是:'.format(str(i)),second_multi(test_list))运行的结果是:第0次运行的结果是:[2,4,6,8]第一次运行的结果是:[4,8,12,16]第二次运行的结果是:[8,16,24,32]可以看出传入了相同的参数test_list每次,3次得到的结果都不一样。这是由于对函数内部的test_list进行了更改。要避免这种情况,只能在函数内部重新定义一个新列表new_list,函数只修改new_list。test_list=[1,2,3,4]defsecond_multi(test_list):new_list=[]foriinrange(len(test_list)):new_list.append(test_list[i]*2)returnnew_listforiinrange(3):print('第{0}次运行的结果是:'.format(str(i)),second_multi(test_list))运行的结果是:第0次运行的结果是:[2,4,6,8]第一次运行的结果是:[2,4,6,8]第二次运行的结果是:[2,4,6,8]但是现在有了函数式编程,可以通过函数式编程上面代码的效果。函数式编程意味着每一段代码都是不可变的并且由纯函数组成。这里的纯函数是指函数本身相互独立,互不影响。对于相同的输入,总会有相同的输出。把上面的函数改成函数式编程怎么样?上面的函数等价于下面的函数式编程map(lambdax:x*2,test_list)test_list=[1,2,3,4]foriinrange(3):print('第{0}次运行的resultis:'.format(str(i)),list(map(lambdax:x*2,test_list)))运算结果为:第0次运算结果为:[2,4,6,8]第一次运行的结果是:[2,4,6,8]第二次运行的结果是:[2,4,6,8]从上面的结果可以看出,函数式编程总会有相同的输出。Python对于函数式编程有以下三个函数。map()函数map()函数的作用是对可迭代对象中的每个元素调用指定的函数,并返回一个map对象。但是这个map对象不能直接输出,可以用for循环或者list()函数来表示。map()函数的语法格式为:map(function,iterable)其中函数参数表示要传入一个函数,可以是内置函数、自定义函数或lambda匿名函数,以及iterable表示一个或多个可迭代对象。可以是列表、字符串等。如下所示:test_list1=[1,3,5]test_list2=[2,4,6]new_map=map(lambdax,y:x+y,test_list1,test_list2)print(list(new_map))操作的结果是:[3,7,11]上面的代码将两个列表中的每个元素相加得到一个新列表。等价于以下函数defadd(test_list1,test_list2):new_list=[]forindexinrange(len(test_list1)):new_list.append(test_list1[index]+test_list2[index])returnnew_listprint(add(test_list1,test_list2))运算结果为[3,7,11]filter()函数filter()函数的作用是利用function函数对iterable中的每一个元素进行判断,返回True或False,最后组合将返回的True元素放入一个新的可迭代过滤器对象集合中。同样,这个filter对象也不能直接输出,可以用for循环或者list()函数来表示。filter函数一般用于过滤数据,其语法格式为:filter(function,iterable)其中function参数表示要传入一个函数,可以是内置函数,也可以是自定义函数,也可以是自定义函数拉姆达函数;iterable表示一个或多个Iterable对象,可以是列表,字符串等,下面是过滤掉列表中的所有偶数。test_list3=[1,2,3,4,5]new_filter=filter(lambdax:x%2==0,test_list3)print(list(new_filter))结果是:[2,4]reduce()函数reduce()函数通常用于对集合做一些累加操作。其语法结构为:importfunctoolsfunctools.reduce(function,iterable)其中,函数参数表示要传入一个函数,可以是内置函数、自定义函数或lambda函数;iterable表示一个或多个可迭代对象,可以是列表、字符串等importfunctoolstest_list4=[1,2,3,4,5]product=functools.reduce(lambdax,y:x+y,test_list4)print(product)运行结果为:15。小结本文详细介绍了Python函数的一些常见进阶知识点,包括函数参数的传递机制、变量的作用域、lambda表达式、函数式编程。都是对实际开发有帮助的知识点。希望对读者朋友有所帮助,也欢迎大家一篇一篇的分享。我是码农飞哥。再次感谢您阅读本文。全网同名【马农飞歌】。不积步,方能达千里。享受分享的快乐。我是码农飞哥。再次感谢您阅读本文。