
如果说之前的序列、分支、循环是代码执行的基础控制方式能顺序、判断、重复执行代码对py有举足轻重的作用那么函数则是对代码进行封装组织。 三者负责控制代码怎么走函数负责把代码打包复用将零散的控制代码整合起来简化程序结构实现重复调用是更高一层的代码组织手段。对于函数这个复杂定义我们要怎么理解呢下面我们一起来看看~一、什么是函数1.作用函数的作用主要是打包代码最大实现代码重复利用减少冗余的代码将不同功能的代码进行封装、分解从而降低代码结构的复杂程度提高代码的可读性。2.分类①BIF内置函数python自带的可直接使用的函数比如print()、len()、max()等等②自定义函数自己用def来定义的函数defdefine关键字标记函数的开始。主要学习这部分内容二、自定义函数基本格式如下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(fMy 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)) # 7x10,y3 print(sub(3, 10)) # -7x3,y10 顺序变结果就变这里的x和y就是位置参数因此我们调用时需要按顺序传递且个数必须一致。否则就会出现错误的结果甚至是报错。但总是去记参数的顺序是件很麻烦的事少量的参数还好若参数一多起来那就困难了~不过不用担心py早就给我们想好方法了⬇️②关键字参数关键字参数只要知道参数的名字即可不用特定的去记参数的位置根据刚刚的例子稍微修改一下。def sub(x, y): return x - y print(sub(10, 3)) # 7x10,y3 print(sub(x10, y3)) # 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,a4,b5) #((1,2,3),4,5)除了将收集参数打包成元组外收集参数还能将参数打包成字典。我们只需在普通形参面前加两个**即可。def example(**nums): print(nums) example(a1,b2,c3) #一定要用关键字参数因为字典里面全是键值对 #{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): zxy return z add(2,3) #5 def add(x,y): return xy add(2,3) #5这两种return写法都能返回add函数的结果。但值得注意的是若函数执行了某个return语句就不会理会后面的语句了直接跳出结果。def add(x,y): if xy10: return 输出结果大于10 else: return xy print(add(6,8)) #输出结果大于10上面这个函数就只会运行到第一个return跳出返回值后便不会再执行后面的else语句。五、变量作用域通常指一个变量可以被访问的范围一个变量的作用域主要由它在代码中被赋值的位置决定。1.局部作用域如果一个变量被定义的位置在一个函数里面那么他的作用域就仅限于该函数这就是局部作用域举个例子。def example(): x2 print(x) example() #2,从函数内部可以访问变量值 print(x) #报错从函数外部访问不到变量值我们能从函数内部访问到变量值但不能从外部访问变量值说明变量x的作用域只限于函数内部这就是局部作用域。2.全局作用域在任何函数的外部去定义一个变量那么这个变量就具有全局作用域。x2 def example(): print(x) example() #2函数内部可以获取变量值 print(x) #2函数外部可以获取变量值聪明的友友们会发现一个问题当函数外部有个具有全局作用域的变量和函数内部具有局部作用域的变量重名了那么到底访问的是哪一个呢x2 def example(): x3 print(x) example() #3访问的是函数内部的变量 print(x) #2访问的是函数外部的变量不难看出若同时具备重名的局部变量和全局变量那么局部变量就会覆盖全局变量。实际上两个x变量只是同名的不同变量我们通过id函数就能发现两个变量的地址还是有差别的。x2 def example(): x3 print(id(x)) example() #140722876543976内部x变量的id地址 print(id(x)) #140722876543944外部x变量的id地址3.global语句既然在函数里面同名的局部变量与外部的全局变量不相等那我们在函数内部该怎么修改全局变量呢global语句会给出答案→x2 def example(): global x x3 print(x) example() #3访问局部变量值不变 print(x) #3访问全局变量值被修改不过值得注意的是在函数中并不提倡global语句来修改变量值因为global语句有时会给整个编程带来许多难修复的bug和高级错误还是尽量少用为好。4.嵌套函数在之前的py学习中我们学习了不少的嵌套其实呢函数也是可以嵌套的并且函数的嵌套在实际操作中还经常用来操作。在嵌套函数中我们要获取被嵌套函数的值往往需要在函数外层就先访问最后才能被打印出来。嗯......这样说可能有点难懂我们举个例子看看def example1(): x5 print(example1变量x的值为,x) def example2(): x10 print(example2变量x的值为,x) example1() #变量x的值为 5 example2() #报错上面是错误示范在example1()这个函数里面我们没有先访问example2()的x值因此最后访问不到example2变量x的值。应该这么写⬇️def example1(): x5 def example2(): x10 print(example2变量x的值为,x) example2() print(example1变量x的值为,x) example1() #example2变量x的值为 10 #example1变量x的值为 55.nonlocal语句与局部变量修改全局变量相同局部变量要修改外部变量的值需要nonlocal语句的帮助。def example1(): x5 def example2(): nonlocal x x10 print(example2变量x的值为,x) example2() print(example1变量x的值为, x) example1() #example2变量x的值为 10 #example1变量x的值为 10加入nonlocal语句修改外部函数使得内函数与外函数的变量相等。6.LEGB规则为了了解变量之间作用域相互影响、相互覆盖的复杂情况py推出了LEGB规则来理清这些关系。LEGB规则localL局部作用域enclosedE嵌套函数的外层函数作用域globalG全局作用域build-inB内置作用域。根据这些顺序越前面的作用域覆盖能力越强。内置作用域指我们把内置函数充当变量时这个变量的作用域。str函数是个好东西这里的str就不是内置函数str()了而是内置变量。因为内置变量的覆盖能力最弱因此我们取变量名时最好不要选内置函数充当变量名。七、闭包1.前提摘要调用内部函数 def example1(): x10 def example2(): print(x) example2() example1() #10我们也可以用下面这个方法调用内部函数def example1(): x10 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(): x10 def example2(): print(x) return example2 exaexample1() exa() #10设置一个“exa”充当新变量可以跳过外层函数example1直接访问到内函数example2。2.正式开始闭包俗称工厂函数工厂主要作用是“来料加工、批量生产”利用嵌套函数来实现工厂的功能就是闭包。本质是嵌套函数内层函数使用外层函数的局部变量外层函数把内层函数作为返回值返回。这里我们调用b站博主小甲鱼《零基础入门学习python》的一个例子来看看→def funA(x): def funB(y): return y**x return funB numfunA(2) #“生产线1”计算平方 print(num(2)) #4 print(num(5)) #25 resultfunA(3) #“生产线2”计算三次方 print(result(2)) #8 print(result(5)) #125funA()相当于一个工厂由于参数不同形成了两个不同的“生产线”。调用num变量时记住的x参数是2而调用result变量时记住的x参数是3,。因此num()函数和result()函数分别对应计算y的平方和y的三次方。两个调用的函数来自同一个工厂函数但记忆的数据完全不同互不影响。根据闭包的这个特性我们可以结合nonlocal语句使外层参数充当记忆载体这样就可以生成一个具有记忆能力的嵌套函数。利用nonlocal语句设计有记忆的嵌套函数 def outer(): x0 y0 def inner(x1,y1): nonlocal x,y xxx1 yyy1 print(fx{x},y{y}) return inner numouter() num(1,2) #x1,y2,第一次算出的结果 num(3,4) #x4,y6第二次算出的结果既然能将参数调用给一个函数使用那当然的也能将一个函数作为参数调用给另一个函数。时间管理大师 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) numtime_master(num) num()一个函数上也可以用多个装饰器def first(func): def inner(): xfunc() return x1 return inner def second(func): def inner(): xfunc() return x*x return inner def third(func): def inner(): xfunc() 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语法应该是这样的deflambda(arg1,arg2...argN): return expression举个例子比较一下传统写法和lambda语法的差距传统写法得到输入值的平方 def num(x): return x**2 print(num(3))lambda语法examplelambda y:y**2 print(example(3))lambda是一个匿名函数适用于解决简单的函数问题大家量力而行的应用即可。十、生成器1.定义在函数的运行时一般遇到return就表示函数即将结束那么有没有什么办法能让函数运行完后还保留状态呢经历过前面的学习大家都知道可以用全局变量或者用闭包的记忆函数。但是大量使用全局变量会污染命名空间闭包操作又过于复杂。因此我们要学习一个新的方法→生成器生成一个生成器其实很简单就是在函数中用yield表达式代替return语句即可。举个例子def num(): i0 while i5: yield i i1 print(num()) #generator object num at 0x00000278A0975B40这样我们就完成了一个生成器的设置但我们得到的结果不是一个返回值而是生成器对象num已经是一个生成器了。要使用这个生成器我们应该调用for循环→def num(): i0 while i5: yield i i1 for i in num(): print(i) #1 #2 #3 #4 #5 #6这里生成器的作用就是每次调用都生成一个数据当程序运行到yield i的时候程序会保留参数状态并输出i的值下一次调用从i1开始执行。我们可以吧生成器看成一个迭代器他有两个特点①不走回头路②支持next()涵数。我们这边执行一下def num(): i0 while i5: yield i i1 cnum() 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 i3: print(还没结束) i1 example(i) example(0) 还没结束 还没结束 还没结束注意要让递归正常运行必须要有一个结束条件这里是if i3并且每次递归都要向着结束条件靠近i1有这样一个过程才能实现递归。递归是一个非常耗费时间的过程因为每一次调用递归函数都不会立即返回而是要等到最底层的那个函数返回然后再一层一层的往上走因此这个过程是非常消耗资源的。十二、函数文档(函数使用说明书)1.查询函数文档在py中使用help()函数就可以快速查看一个函数的使用文档了查看print函数文档 help(print) Help on built-in function print in module builtins: print(*args, sep , end\n, fileNone, flushFalse) 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__查询函数名def area(r,π3.14): pass print(area.__name__) #area②自定义函数.__doc__查询函数文档def area(r,π3.14): 功能计算圆的面积 参数 -r:圆的半径 -π:圆周率周长/直径的固定值 返回值 -圆的面积 return r*π print(area.__doc__)③自定义函数.__annotations__查询函数类型注释def add(a:str,b:int)-str: return a*b print(add.__annotations__) #{a: class str, b: class int, return: class str}后续的内省知识在学习类的时候再了解能更加领会。十五、高阶函数我们知道函数也能像参数那样被传来传去当一个函数接收另一个函数作为参数时这种函数我们称之为高阶函数。时间管理大师 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) numtime_master(num) num()像这个装饰器的例子我们就可以说time_master是一个高阶函数。在python中高阶函数作用巨大因此py专程为此搞了一个模块“functools”方便大家直接调用高阶函数。感兴趣的伙伴们可以了解一下这个模块用处还是非常之大的。十六、总结函数是一个比较抽象的东西大家看完之后还是不太理解也是正常的可以找找能拿来练手的书籍或网上查找例题试试相信最终大家都能掌握。本博文内容还有些不够全面毕竟函数的知识是学不完的只指点了一些基础知识。如果大家发现本博文有什么错误之处请立即指出我会马上改正谢谢阅读(^_^)