函数的基础知识
函数
函数的一个参数不可以传多次,只可以传一次(这个传多次主要是在关键字参数传值上才会出现的),包括后面将的二星收集参数
def func(name,words): """这是一个函数""" print(name+"->"+words) 1:位置参数,形参按顺序接受实参,如 func("djh","好帅")//name="djh",words="好帅" 2:关键字参数 func(name="djh",words="好帅")
需要注意的是,位置参数可以和关键字参数混着来用,但是位置参数必须全部在关键字参数前面
这两个在默认参数上的区别
def func(name="djh",words="好帅"): """这是一个函数""" print(name+"->"+words)
>>>func("xxx")//"xxx->好帅",第一个参数接受实参
>>>func(words = "非常帅")//"djh->非常帅",可以不按顺序接受实参
//收集参数 --一般而言收集参数都是放在最后的,而二星收集参数放在更后
1:将参数打包成元组 ---- 一个*号 ----- 其实就是收集数量不定的位置参数,将这些位置参数打包成元组
收集参数放在普通参数后面---最后面
def func(x,y,*args): print(x) print(y) print(args) func(1,"a",1,2,3,"a","b","ddsd") 结果 1 a (1, 2, 3, 'a', 'b', 'ddsd')//它会将前面的两个自动赋给普通参数,后面的全部打包成元组赋给收集参数,注意:没有默认值的普通参数必须要有传值,但收集参数不一定要有传值 def func(x,y,*args): print(x) print(y) print(args) func(1,"a") 结果 1 a ()//收集参数没有传值,没有默认值的普通参数必须传值
收集参数不是放在最后面,对于在收集参数前面的参数可以直接位置传值,但是在收集参数后面的普通参数必须用关键字传值
def func(x,*args,y): print(x) print(y) print(args) func(1,"a",2,"s") 结果 TypeError: func() missing 1 required keyword-only argument: 'y' def func(x,*args,y): print(x) print(y) print(args) func(1,"a",2,"s",y=5)//传值的时候,用关键字传值的y必须写在收集参数后面不然会报错 结果 1 5 ('a', 2, 's') def func(x,*args,y): print(x) print(y) print(args) # print(kwargs) func(1,y="x",1,2,4,1) 结果 SyntaxError: positional argument follows keyword argument
一星收集参数的其他一些杂项
def func(x,*args): print(x) print(args) func(1,["a",2,"s"]) 结果 1 (['a', 2, 's'],)//["a",2,"s"]参数看成一个整体,所以就将这一个参数打包成一个元组,也就是如你看到的,一个元组里有一个元素,这个元素是一个列表 要想传入是一个列表,想要打包成元素传给收集参数的也是一个列表则可以一下做法,解包 其实解包就是让python进行for循环一个一个遍历取出来后赋给收集参数,然后就打包成 一个元组咯 如果是字典前只加一个*会怎样, ---答案:会将字典的全部key取出来打包成一个元组 def func(x,*args): print(x) print(args) func(1,*["a",2,"s"]) 结果 1 ('a', 2, 's')
2:将参数打包成字典 ---- 两个* ----其实就是收集将数量不定关键字参数打包成字典
二星收集参数必须放在所有参数后面,包括一星收集参数,因为二星是关键字参数一星是位置参数,如果一星在后面就违反了大原则关键字参数必须全部在位置参 数后面
def func(**kwargs,x): print(x) print(kwargs) func(x=1,a=1,b=2) 结果 SyntaxError: invalid syntax//二星收集参数必须在所有参数前面 def func(x,**kwargs): print(x) print(kwargs) func(1,2,3,4,a="B",c=2) 结果 TypeError: func() takes 1 positional argument but 4 were given//位置参数只需要1个这里给了4个且没有一星收集参数 def func(**kwargs): # print(x) print(kwargs) func(**{"X":1,"w":2,"k":"a"}) 结果 {'X': 1, 'w': 2, 'k': 'a'} 但是如果不解包的话就会报错,并且因为二星收集参数是收集关键字参数的,所有如果这里要传一个字典,则该字典的key必须全部是字符串
在最后给一个所有参数都用上的标准的形式
def func(x,y,*args,**kwargs): print(x) print(y) print(args) print(kwargs) func(1,2,3,4,"A","n",a="X",b=3) 结果 1 2 (3, 4, 'A', 'n') {'a': 'X', 'b': 3}
函数的局部变量和全局变量的关系
在主程序里定义的变量是全局变量,在函数内部定义的变量是局部变量,在函数内部访问变量的过程原理是这样的:先在函数自己的作用域内寻找某个需要访问的变量,如果找不到则向外层找,直到找到为止,如果到最外层也就是主程序那一层了都找不到则报错,这个是访问的过程,如果是创建变量的过程则直接在自己的那层作用域创建就好,如果名字与外层的变量名相等,那外层的变量名自然就被覆盖咯,因为是在自己的层次开始找嘛,自然就会在自己的层次先找到咯!下面开始举例说明:
def func(): print(name)//现在自己的层次找,找不到所以往外层找,找到了主程序的那层的name变量 name="djh" func() 结果 djh def func(): name="DJH"//在自己的层次里创建name变量 print(name)//在自己的层次里找到了name变量 name="djh" func() print(name)//所谓的看起来像是修改了全局的name变量不过是在func函数内部创建了一个同名的name局部变量,所以全局的name变量没有被修改 结果 DJH djh
但如果真想要在函数内部对全局变量进行修改,则需要在函数内部给这个函数权限 ----- global 关键字
def func(): global name//在函数内部给这个函数权限,让它可以直接引用主程序那层的name变量 name="DJH"//这里就不是在函数内部创建一个name的局部变量了,而是直接对主程序那层的name全局变量进行修改,因为已经有权限了嘛 print(name) name="djh" func() print(name) 结果 DJH DJH
关于变量的一些杂项,如果全局变量是一个列表有一下几种情况:
def func(): x[0]="DJH"//列表比较特殊,因为它存放的地方不是栈区,这里是直接就修改全局变量的x的第一个元素,一旦这里和全局变量的x联系上了,就不可以再在该局部变量的区域内定义名字 为x的变量了,不然就报错,这里是比较特殊的地方,不需要 global 赋予权限 print(x)//已经和全局变量的x联系上了,输出的自然就全局变量的x x = ["djh","DJH"] func() print(x) 结果 ['DJH', 'DJH'] ['DJH', 'DJH'] def func(): x[0]="DJH" x=111//不可以再创建名字为x的变量 print(x) x = ["djh","DJH"] func() print(x) 结果 UnboundLocalError: local variable 'x' referenced before assignment def func(): x=1111//一开始还未联系上自然就可以创建名字为x的局部变量 print(x) x = ["djh","DJH"] func() print(x) 结果 1111 ['djh', 'DJH'] def func():// global x//一开始未联系上,直接给权限自然就可以修改全局变量的x x=1111 print(x) x = ["djh","DJH"] func() print(x) 结果 1111 1111 其实一句话讲噻,先看你一开始有没有和这个全局变量x联系,如果有联系,则不可以再创建同名的局部变量,但是可以直接修改这个全局变量x列表的元素不可以修改整个x,什么意思呢,就 是说,不可以给整个x重新赋值,比如x=111,但是可以给它的元素赋值,如果一开始没联系,那该怎么来就怎么来就怎么来,该创建同名的局部变量就创建,但如果一开始给的是global权限 那就可以直接整个修改 def func(): print(x)//一开始就联系上了 x=111//不可以整个修改 x = ["djh","DJH"] func() print(x) 结果 UnboundLocalError: local variable 'x' referenced before assignment
//函数内部变量之间的关系
外部函数的变量与内部函数的变量,他们之间的关系和上面讲的全局变量和局部变量之间的关系完全相同,什么先找自己这层呀,同名覆盖呀之类的都一样,甚至,对于列表而言,在外部函数定义的列表在内部函数去访问修改的,都和上面的全局变量的列表在局部变量修改的一模一样,什么如果联系上了会怎么样,没联系上又会怎么样,都一样, 这里加一个关键字 nonlocal 和 global一样是给予权限的关键字,只不过nonlocal是外部函数的局部变量的权限给予给内部函数,相当于在函数内用global关键字把全局变量的权限给予给函数
def fun1(): x = [1,2,3] def fun2(): nonlocal x x=222 print(x) fun2() print(x) fun1() 结果 222 222
//高阶函数
返回一个内部函数的函数其实也是返回一个函数对吧,所以就叫高阶函数 --- 其实说白了就是想在主程序里调用内部函数,因为一般情况下在主程序里调用内部函数是错误的,高阶函数就为这种需求提供了方法
name = "djh" def func(): name="DJH" def fun1(): print(name) return fun1//这里返回的是内部函数的名字,也就是内部函数的内存地址 func()()//func()这个执行是返回fun1()函数的内存地址,也就是说func()等价于fun1,func()相当于fun1函数的别名 然后func()()相当于fun1(),执行fun1()函数所在的内存 的代码块,也就是fun1()这个函数,从这个函数所在层次开始调用,比如说fun1()这个函数是在func()函数内部的,故func()()--->print(name)的时候是先在自己层找,找不到name再 在func()这个上一个层次的函数里找 结果 DJH
//匿名函数 ---- 返回一个内存地址,以该内存地址为开始的连续空间内存储的就是该匿名函数的代码
lambda函数返回一个内存地址 后面的表达式相当于一个真正函数的return后面的表达式 g=lambda x:x**x//g变量接收lambda匿名函数的起始内存地址,相当于g是这个匿名函数的名字咯 print(g(2)) 结果 4 当然也可以如下 print((lambda x:x**x)(2))//(lambda x:x**x)返回一个内存地址,后面加个(2)相当于调用这个函数,传入参数2 结果 4 如果要返回多个值,需要将其打包成元组,普通函数直接逗号隔开,python帮你打包,lambda函数只可以自己打包,python不帮你打包 g = lambda x,y,z:(x+1,y+1,z+1) print(g(1,2,3)) 结果 (2,3,4)
//高阶函数 --- 函数的参数是另一个函数的函数名,或者返回值是另一个函数的函数名
def func(x,y,z): print(z(x)+z(y)) func(-1,-4,abs)//传入的参数是另一个函数的函数名 -- 高阶函数 结果 5 def fun1(x): def fun2(y): return x+y return fun2//返回值是另一个函数的函数名 -- 高阶函数 print(fun1(2)(3)) 结果 5
//尾递归优化
啥叫尾递归呢 就是在一个函数的最后一步才调用另一个函数(当然可以是自己这个函数也可以是其他函数),因为在平时,我们的递归(也就是调用自己本身这个函数)不是在最后一步递归,则这一层就需要被记录,因为这个递归的函数下面的代码没被执行自然需要保存这一层,直到递归函数执行结束返回到这一层才重新执行下面的代码,但如果是尾递归也就是在最后一步才调用递归函数,则对于这一层的函数而言全部代码都已经执行完毕,进入下一层的时候就可以直接丢弃上一层,所以就不用保存就做到了优化
//map()函数
先给一个类似map()内置函数的原理代码,不完全一样,map()内置函数返回的是一个迭代器,而下面给的代码返回的是一个列表,差了点但不多!
def map_test(func,arry)://map_test()这个函数就是类似map()的原理搞出来的 res = [] for each in arry: res.append(func(each)) return res a = map_test(lambda x:x+1,[1,2,3,4]) print(a) //第一个参数是一个函数名,也就是某个函数的内存地址,第二个参数就是待处理的列表,将列表中的每个元素代入第一个函数参数中得出得结果塞进一个列表中然后返回这个列表 结果 [2,3,4,5]
用map()函数实现
res = map(lambda x:x+1,[1,2,3,4]) a = list(res) print(res) print(a) 结果 <map object at 0x000001DBF52A9208>//迭代器对象 [2, 3, 4, 5]//列表 //第一个参数是一个函数名,也就是某个函数的内存地址,第二个参数就是待处理的列表,将列表中的每个元素代入第一个函数参数中得出得结果塞进一个迭代器中然后返回这个迭代器,然后将 这个迭代器传给list()函数返回一个列表
//filter()函数
先给一个类似filter()内置函数的原理代码,不完全一样,filter()内置函数返回的是一个迭代器,而下面给的代码返回的是一个列表,差了点但不多!
def fun(func,arry): res=[] for each in arry: if func(each): res.append(each) return res arry=["sb_hyt","sb_HYT","sbsb_hyt","nb_djh","nb_DJH"] res=fun(lambda x:not x.startswith("sb"),arry) print(res) //第一个参数是一个函数名,也就是某个函数的内存地址,第二个参数就是待处理的列表,将列表中的每个元素代入第一个函数参数中进行判断,如果判断结果为真则将其塞进一个列表中, 如果为假则不塞进列表中然后返回这个列表 结果 ['nb_djh', 'nb_DJH']
用filter()函数实现
arry=["sb_hyt","sb_HYT","sbsb_hyt","nb_djh","nb_DJH"] res = filter(lambda x:not x.startswith("sb"),arry) a = list(res) print(res) print(a) 结果 <filter object at 0x00000203BF0C59E8>//和map()函数一样,返回一个迭代器 ['nb_djh', 'nb_DJH']//列表 //第一个参数是一个函数名,也就是某个函数的内存地址,第二个参数就是待处理的列表,将列表中的每个元素代入第一个函数参数中进行判断, 如果判断结果为真则将其塞进一个迭代器中,如果为假则不塞进迭代器中然后返回这个迭代器,然后再传入list()函数中得到列表
//reduce()函数
先给一个reduce()函数得源码,实现原理就是下面得代码
def reduce_test(func,arry,init=None): if init is None: res=arry.pop(0) else: res=init for each in arry: res=func(res,each) return res res1=reduce_test(lambda x,y:x*y,[1,2,3,4,100])//没起始值,由默认值那里开始 res2=reduce_test(lambda x,y:x*y,[1,2,3,4,100],10)//有起始值,从起始值那里开始 print(res1,res2) //第一个参数是一个函数得内存地址,表示要怎么处理列表元素(全部加还是全部减),第二个参数是列表(元组也可以啦),第三个参数是起始值,从哪里开始算 结果 2400 24000 用内置得reduce()函数实现 from functools import reduce res=reduce(lambda x,y:x*y,[1,2,3,4,100],100)//省了一大堆自己写得原型,直接调用,但是要记住,reduce()函数被集成在一个模块中了,用时要先声明 print(res) 结果 240000