python的函数知识点
如果说之前的序列、分支、循环是代码执行的基础控制方式,能顺序、判断、重复执行代码,对py有举足轻重的作用;那么函数则是对代码进行封装组织。 三者负责控制代码怎么走,函数负责把代码打包复用,将零散的控制代码整合起来,简化程序结构,实现重复调用,是更高一层的代码组织手段。
对于函数这个复杂定义,我们要怎么理解呢,下面我们一起来看看~
一、什么是函数
1.作用
函数的作用主要是打包代码,最大实现代码重复利用,减少冗余的代码;将不同功能的代码进行封装、分解,从而降低代码结构的复杂程度,提高代码的可读性。
2.分类
①BIF内置函数,python自带的,可直接使用的函数,比如print()、len()、max()等等
②自定义函数,自己用def来定义的函数,def:define关键字,标记函数的开始。(主要学习这部分内容)
二、自定义函数
基本格式如下:
def 函数名(参数列表): 函数体(执行代码) return 返回值我们定义一个最简单的函数
"""设置一个占位用的函数""" def first(): pass这里的自定义函数first(),没有什么实质性作用,主要是给某个要实现的功能先占个位置,pass相当于占一个坑的意思,若要完善这个函数,我们只需要替换掉这个pass即可。
def first(): pass def first(): #重新完善这个函数 for i in range(3): print("hello world") first() #调用这个函数 #hello world #hello world #hello world三、函数参数
1.函数参数通用概念
函数的参数主要分为形式参数(形参)和实际参数(实参),我们先简单的写一个自定义函数
def grade(Chinese,times): for i in range(times): print(f"My Chinese score is {Chinese}") grade(90,3) #My Chinese score is 90 #My Chinese score is 90 #My Chinese score is 90这里的Chinese和times就是形式参数(形参),指定义函数时设定的值。
而90和3则是实际参数(实参),指调用函数时传递进去的值。
提示:这里学完可以先学目录四(函数的返回值)再来学习后续的参数知识点。
2.函数参数的分类
①位置参数
在py的函数中,位置固定的参数称之为位置参数。例子如下
def sub(x, y): return x - y print(sub(10, 3)) # 7,x=10,y=3 print(sub(3, 10)) # -7,x=3,y=10 顺序变结果就变!这里的x和y就是位置参数,因此我们调用时需要按顺序传递,且个数必须一致。否则就会出现错误的结果甚至是报错。
但总是去记参数的顺序,是件很麻烦的事,少量的参数还好,若参数一多起来,那就困难了~不过不用担心,py早就给我们想好方法了⬇️
②关键字参数
关键字参数,只要知道参数的名字即可,不用特定的去记参数的位置,根据刚刚的例子稍微修改一下。
def sub(x, y): return x - y print(sub(10, 3)) # 7,x=10,y=3 print(sub(x=10, y=3)) # 7我们可以清晰的看出,只要给特定的参数赋值即可。
如果我们要同时使用位置参数和关键字参数,需要把位置参数设定在关键字参数前面,否则程序会报错。
def world(a,b,c): return "".join((a,b,c)) print(world(a="今天","天气","真好")) #报错,关键字参数在位置参数前面 print(world("今天","天气",c="真好")) #今天天气真好③默认参数
在定义函数(def)的时候,直接给形参赋值,这个参数就是默认参数。 调用函数时,如果不给这个参数传实参,它自动使用预先写好的默认值;如果传了值,就覆盖默认值。
def world(a,b,c="踢球"): return "".join((a,b,c)) print(world("小明","喜欢")) #小明喜欢踢球,不给实参输出默认值 print(world("小明","喜欢","游泳")) #小明喜欢游泳,给实参输出实参值设置默认参数时应放在自定义函数括号内的最后面,否则会报错。
def world(a="小明",b,c="踢球"): return "".join((a,b,c)) print(world("喜欢")) #报错,默认参数没放最后面 def world(b,a="小明",c="踢球"): return "".join((a,b,c)) print(world("喜欢")) #小明喜欢踢球,默认参数全部放在后面,正常输出④收集参数
有时候,我们设置一个函数时并不知道一共需要输入多少参数,前面几种参数都是一一对应的,因此我们需要学习一种参数——收集参数,来解决这个问题。
那么,我们要怎么定义一个收集参数呢?其实办法也很简单,我们只要在一个普通的形参前面加一个*即可。这里我们利用format()内置函数(获取数据,并填补占位符{}的空白)做个例子。
def example(*nums): print("有{}数字".format(len(nums))) print("第3个元素是{}".format(nums[2])) example(1,2,3,4,5) #有5数字 #第3个元素是3可以看出,这里我们给nums这个形参定义了五个实参,这个*nums就是收集参数。
那收集参数的背后逻辑到底是什么呢?我们直接将nums打印出来看看
def example(*nums): print(nums) example(1,2,3,4,5) #(1, 2, 3, 4, 5)最后的结果实际上是一个元组,那么为什么会是元组呢?因为元组具有打包和解包的能力,这里就用到了元组的打包。
如果设置的形参里面不仅仅只有一个收集参数,还有其他的形参,那我们需要用关键字参数定义其他的形参,举个例子。
def example(*nums,a,b): print(*nums,a,b) example(1,2,3,4,5) #报错,没有定义形参参数a和b example(1,2,3,a=4,b=5) #((1,2,3),4,5)除了将收集参数打包成元组外,收集参数还能将参数打包成字典。我们只需在普通形参面前加两个**即可。
def example(**nums): print(nums) example(a=1,b=2,c=3) #一定要用关键字参数,因为字典里面全是键值对 #{'a': 1, 'b': 2, 'c': 3}⑤解包参数
在形参上使用*和**我们称之为收集参数,也就是参数的打包;那么当*和**用在实参上我们泽称之为解包参数,就是参数的解包。
x=(1,2,3,4) #定义一个元组 def example(a,b,c,d): print(a,b,c,d) example(*x) #*x为解包参数 #1 2 3 4两个星号,也是一样,将字典进行解包
x= {"a":1,"b":2,"c":3} def example(a,b,c): print(a,b,c) example(**x) #1 2 3四、函数的返回值
FBI内置函数一般用len()、sum()这些函数返回一些结果。
自定义函数一般用return语句返回。
def add(x,y): z=x+y return z add(2,3) #5 def add(x,y): return x+y add(2,3) #5这两种return写法都能返回add函数的结果。但值得注意的是,若函数执行了某个return语句,就不会理会后面的语句了,直接跳出结果。
def add(x,y): if x+y>=10: return "输出结果大于10" else: return x+y print(add(6,8)) #输出结果大于10上面这个函数就只会运行到第一个return,跳出返回值后便不会再执行后面的else语句。
五、变量作用域
通常指一个变量可以被访问的范围,一个变量的作用域主要由它在代码中被赋值的位置决定。
1.局部作用域
如果一个变量被定义的位置在一个函数里面,那么他的作用域就仅限于该函数,这就是局部作用域,举个例子。
def example(): x=2 print(x) example() #2,从函数内部可以访问变量值 print(x) #报错,从函数外部访问不到变量值我们能从函数内部访问到变量值,但不能从外部访问变量值,说明变量x的作用域只限于函数内部,这就是局部作用域。
2.全局作用域
在任何函数的外部去定义一个变量,那么这个变量就具有全局作用域。
x=2 def example(): print(x) example() #2,函数内部可以获取变量值 print(x) #2,函数外部可以获取变量值聪明的友友们会发现一个问题,当函数外部有个具有全局作用域的变量和函数内部具有局部作用域的变量重名了,那么到底访问的是哪一个呢?
x=2 def example(): x=3 print(x) example() #3,访问的是函数内部的变量 print(x) #2,访问的是函数外部的变量不难看出,若同时具备重名的局部变量和全局变量,那么局部变量就会覆盖全局变量。
实际上,两个x变量只是同名的不同变量,我们通过id函数就能发现,两个变量的地址还是有差别的。
x=2 def example(): x=3 print(id(x)) example() #140722876543976,内部x变量的id地址 print(id(x)) #140722876543944,外部x变量的id地址3.global语句
既然在函数里面同名的局部变量与外部的全局变量不相等,那我们在函数内部该怎么修改全局变量呢?global语句会给出答案→
x=2 def example(): global x x=3 print(x) example() #3,访问局部变量值不变 print(x) #3,访问全局变量值被修改不过值得注意的是,在函数中并不提倡global语句来修改变量值,因为global语句有时会给整个编程带来许多难修复的bug和高级错误,还是尽量少用为好。
4.嵌套函数
在之前的py学习中,我们学习了不少的嵌套,其实呢函数也是可以嵌套的,并且函数的嵌套在实际操作中还经常用来操作。
在嵌套函数中,我们要获取被嵌套函数的值,往往需要在函数外层就先访问,最后才能被打印出来。嗯......这样说可能有点难懂,我们举个例子看看:
def example1(): x=5 print("example1变量x的值为",x) def example2(): x=10 print("example2变量x的值为",x) example1() #变量x的值为 5 example2() #报错上面是错误示范,在example1()这个函数里面,我们没有先访问example2()的x值,因此最后访问不到example2变量x的值。应该这么写⬇️
def example1(): x=5 def example2(): x=10 print("example2变量x的值为",x) example2() print("example1变量x的值为",x) example1() #example2变量x的值为 10 #example1变量x的值为 55.nonlocal语句
与局部变量修改全局变量相同,局部变量要修改外部变量的值,需要nonlocal语句的帮助。
def example1(): x=5 def example2(): nonlocal x x=10 print("example2变量x的值为",x) example2() print("example1变量x的值为", x) example1() #example2变量x的值为 10 #example1变量x的值为 10加入nonlocal语句,修改外部函数,使得内函数与外函数的变量相等。
6.LEGB规则
为了了解变量之间作用域相互影响、相互覆盖的复杂情况,py推出了LEGB规则,来理清这些关系。
LEGB规则:local(L)局部作用域,enclosed(E)嵌套函数的外层函数作用域,global(G)全局作用域,build-in(B)内置作用域。根据这些顺序,越前面的作用域覆盖能力越强。
内置作用域指我们把内置函数充当变量时,这个变量的作用域。
str="函数是个好东西"这里的str就不是内置函数str()了,而是内置变量。因为内置变量的覆盖能力最弱,因此我们取变量名时,最好不要选内置函数充当变量名。
七、闭包
1.前提摘要
"""调用内部函数""" def example1(): x=10 def example2(): print(x) example2() example1() #10我们也可以用下面这个方法调用内部函数
def example1(): x=10 def example2(): print(x) return example2 print(example1()) #<function example1.<locals>.example2 at 0x000002193320D260>,example1()=example2 example1()() #10,example1()()=example2()利用返回的方法,返回出example2这个函数对象。后面的example1()这个整体就代表example2这个函数本身,所以example1()()等于example2(),即执行example2这个函数。
我们也可以这样写:
def example1(): x=10 def example2(): print(x) return example2 exa=example1() exa() #10设置一个“exa”充当新变量,可以跳过外层函数example1直接访问到内函数example2。
2.正式开始
闭包,俗称工厂函数,工厂主要作用是“来料加工、批量生产”,利用嵌套函数来实现工厂的功能就是闭包。本质是嵌套函数+内层函数使用外层函数的局部变量+外层函数把内层函数作为返回值返回。
这里我们调用b站博主小甲鱼《零基础入门学习python》的一个例子来看看→
def funA(x): def funB(y): return y**x return funB num=funA(2) #“生产线1”,计算平方 print(num(2)) #4 print(num(5)) #25 result=funA(3) #“生产线2”,计算三次方 print(result(2)) #8 print(result(5)) #125funA()相当于一个工厂,由于参数不同形成了两个不同的“生产线”。调用num变量时,记住的x参数是2,而调用result变量时,记住的x参数是3,。因此num()函数和result()函数分别对应计算y的平方和y的三次方。两个调用的函数来自同一个工厂函数,但记忆的数据完全不同,互不影响。
根据闭包的这个特性,我们可以结合nonlocal语句,使外层参数充当记忆载体,这样就可以生成一个具有记忆能力的嵌套函数。
"""利用nonlocal语句,设计有记忆的嵌套函数""" def outer(): x=0 y=0 def inner(x1,y1): nonlocal x,y x=x+x1 y=y+y1 print(f"x={x},y={y}") return inner num=outer() num(1,2) #x=1,y=2,第一次算出的结果 num(3,4) #x=4,y=6,第二次算出的结果既然能将参数调用给一个函数使用,那当然的也能将一个函数作为参数调用给另一个函数。
"""时间管理大师""" import time #调用time模块 def time_master(func): print("开始运行程序") start = time.time() func() stop = time.time() print("程序运行结束") print(f"总共花费了{(stop-start):.2f}秒") def num(): time.sleep(1) #程序运行太快,先让程序沉睡1秒 print("hello world") time_master(num) #将num函数当做参数调到time_master函数里 """开始运行程序 hello world 程序运行结束 总共花费了1.00秒"""八、装饰器
在工作中,作为程序员的你往往是接手小组任务的选手,但你的小组成员写出来的代码是满身bug却能正常运行的“屎山代码”,这时你该怎么办呢?这种代码改一行则动全身,是一种难缠的存在,因此大多数程序员会选择保护而不是改变。
装饰器就是这件保护代码的金钟罩铁布衫→
"""时间管理大师""" import time def time_master(func): def callfunc(): print("开始运行程序") start = time.time() func() stop = time.time() print("程序运行结束") print(f"总共花费了{(stop-start):.2f}秒") return callfunc @time_master def num(): time.sleep(1) print("hello world") num() """开始运行程序 hello world 程序运行结束 总共花费了1.00秒"""依照上面的时间管理大师例子,我们稍微修改,这样,不调用time_master函数,依旧能输出结果。认真的阅读代码,我们会发现装饰器其实就是闭包+将函数当做参数调用,两者的结合。
至于@+装饰器,实则是一个语法糖。装饰器原来的样子长这样:
"""时间管理大师""" import time def time_master(func): def callfunc(): print("开始运行程序") start = time.time() func() stop = time.time() print("程序运行结束") print(f"总共花费了{(stop-start):.2f}秒") return callfunc def num(): time.sleep(1) print("hello world") num=time_master(num) num()一个函数上也可以用多个装饰器
def first(func): def inner(): x=func() return x+1 return inner def second(func): def inner(): x=func() return x*x return inner def third(func): def inner(): x=func() return x*x*x return inner @first @second @third def num(): return 2 print(num()) #65根据结果我们可以知道,py先运行的是装饰器@third,最后运行的才是@first。
九、lambda表达式
lambda表达式俗称匿名函数,掌握lambda表达式你也能写出“一行流”代码,用简短的代码解决复杂的问题。lambda表达式体现你对py函数的掌握能力,是高手们常用的方法。
"""lambda语法""" lambda arg1 arg2 arg3...argN:expressionlambda在这里是关键字,冒号左边指传入函数的参数,冒号右面是函数实现表达式以及返回值。我们可以把lambda语句看做一个精简的函数,如果我们用传统函数的定义方式来写lambda语法应该是这样的:
def<lambda>(arg1,arg2...argN): return expression举个例子比较一下传统写法和lambda语法的差距,传统写法:
"""得到输入值的平方""" def num(x): return x**2 print(num(3))lambda语法:
example=lambda y:y**2 print(example(3))lambda是一个匿名函数,适用于解决简单的函数问题,大家量力而行的应用即可。
十、生成器
1.定义
在函数的运行时,一般遇到return就表示函数即将结束,那么有没有什么办法能让函数运行完后还保留状态呢?经历过前面的学习大家都知道,可以用全局变量或者用闭包的记忆函数。但是大量使用全局变量会污染命名空间,闭包操作又过于复杂。因此我们要学习一个新的方法→生成器
生成一个生成器其实很简单,就是在函数中用yield表达式代替return语句即可。举个例子
def num(): i=0 while i<=5: yield i i+=1 print(num()) #<generator object num at 0x00000278A0975B40>这样,我们就完成了一个生成器的设置,但我们得到的结果不是一个返回值,而是生成器对象,num已经是一个生成器了。
要使用这个生成器,我们应该调用for循环→
def num(): i=0 while i<=5: yield i i+=1 for i in num(): print(i) #1 #2 #3 #4 #5 #6这里生成器的作用就是每次调用都生成一个数据,当程序运行到yield i的时候,程序会保留参数状态,并输出i的值,下一次调用从i+=1开始执行。
我们可以吧生成器看成一个迭代器,他有两个特点,①不走回头路,②支持next()涵数。我们这边执行一下
def num(): i=0 while i<=5: yield i i+=1 c=num() print(next(c)) #0 print(next(c)) #1 print(next(c)) #2 print(next(c)) #3 print(next(c)) #4 print(next(c)) #5 print(next(c)) #报错注意:由于生成器每次调用获取一个结果的特性,并不能用下标索引的方法获取生成器内的元素。
2.生成器表达式
如果我们将列表推导式的方括号换成圆括号会发现什么
t=(i**2 for i in range(10)) print(t) #<generator object <genexpr> at 0x000002558145AA80>可以看出来打印的是一个生成器表达式,因此只要改变括号类型就能有生成器表达式。
t=(i**2 for i in range(10)) for i in t: print(i)加上for循环遍历打印每个结果,这就是运行生成器表达式的方法。
生成器表达式和列表推导式最大的区别是,列表推导式会一下子打印全部的结果,而生成器表达式只会一个一个的打印。
十一、递归
递归,实质上就是函数调用自身的过程。举个例子
def example(i): if i<3: print("还没结束") i+=1 example(i) example(0) '''还没结束 还没结束 还没结束'''注意:要让递归正常运行,必须要有一个结束条件(这里是if i<3),并且每次递归都要向着结束条件靠近(i+=1),有这样一个过程才能实现递归。
递归是一个非常耗费时间的过程,因为每一次调用递归函数都不会立即返回,而是要等到最底层的那个函数返回,然后再一层一层的往上走,因此这个过程是非常消耗资源的。
十二、函数文档(函数使用说明书)
1.查询函数文档
在py中使用help()函数,就可以快速查看一个函数的使用文档了
'''查看print函数文档''' help(print) '''Help on built-in function print in module builtins: print(*args, sep=' ', end='\n', file=None, flush=False) Prints the values to a stream, or to sys.stdout by default. sep string inserted between values, default a space. end string appended after the last value, default a newline. file a file-like object (stream); defaults to the current sys.stdout. flush whether to forcibly flush the stream.'''2.创建函数文档
只需要将函数的文档内容写到函数内部即可,比如
def area(r,π=3.14): """ 功能:计算圆的面积 参数: -r:圆的半径 -π:圆周率(周长/直径的固定值) 返回值: -圆的面积 """ return r*π这样我们便完成了一次函数文档的创建,接下来用help()方法就能查看自定义函数文档了
help(area) """Help on function area in module __main__: area(r, π=3.14) 功能:计算圆的面积 参数: -r:圆的半径 -π:圆周率(周长/直径的固定值) 返回值: -圆的面积"""十三、类型注释
有时你会看到网上或书籍上有些大神会写这样的代码
def add(a:str,b:int)->str: return a*b print(add("小明",5)) #小明小明小明小明小明这个函数的意思就是,希望传入的a是字符串类型,b是整数型,得出的结果是字符串类型。但如果你不按要求传入参数,py也不会出面报错的
def add(a:str,b:int)->str: return a*b print(add(3,5)) #15像这种要求操作者输入特定的类型参数,但又不会被电脑识别的就是类型注释,类型注释主要是给人看的,而不是给机器看的。
十四、内省(自省)
指在程序运行的时候,能够进行自我检测的机制。
基于现在的知识储备,现在只讲查阅函数的这部分内省知识。查阅函数本身,应该是函数最基本的内省机制了。
| 序号 | 方法 | 作用 | 示例 |
| ① | 自定义函数.__name__ | 查询函数名 | |
| ② | 自定义函数.__doc__ | 查询函数文档 | |
| ③ | 自定义函数.__annotations__ | 查询函数类型注释 | |
后续的内省知识在学习类的时候再了解能更加领会。
十五、高阶函数
我们知道函数也能像参数那样被传来传去,当一个函数接收另一个函数作为参数时,这种函数我们称之为高阶函数。
"""时间管理大师""" import time def time_master(func): def callfunc(): print("开始运行程序") start = time.time() func() stop = time.time() print("程序运行结束") print(f"总共花费了{(stop-start):.2f}秒") return callfunc def num(): time.sleep(1) print("hello world") num=time_master(num) num()像这个装饰器的例子,我们就可以说time_master是一个高阶函数。
在python中高阶函数作用巨大,因此py专程为此搞了一个模块“functools”,方便大家直接调用高阶函数。感兴趣的伙伴们可以了解一下这个模块,用处还是非常之大的。
十六、总结
函数是一个比较抽象的东西,大家看完之后还是不太理解也是正常的,可以找找能拿来练手的书籍或网上查找例题试试,相信最终大家都能掌握。
本博文内容还有些不够全面(毕竟函数的知识是学不完的),只指点了一些基础知识。如果大家发现本博文有什么错误之处请立即指出,我会马上改正!!!
谢谢阅读(^_^)
