一、函数基础简单来说,函数就是程序中可以运行一次或多次的Python语句的组合。Python中的函数在其他语言中也称为过程或子例程,因此这些包装语句由函数名调用。有了函数,我们就可以大大减少复制粘贴代码的次数(相信很多人一开始都有这种体会)。我们可以提取相同的代码来制作一个函数,只需要在需要的地方调用它。那么,这就提高了代码的复用率,整体代码看起来更简洁,不那么臃肿。函数是Python中非常基础的程序结构,用来充分复用我们的代码;同时,函数可以将一个复杂的系统划分为可管理的部分,简化编程和代码重用。接下来,让我们看看函数是什么以及它应该如何定义。有两种定义函数的方法,即def和lambda关键字。1.函数定义先总结一下为什么要用函数?最大化代码重用,最小化冗余代码;过程分解(拆卸)。将一个复杂的任务分解成多个较小的任务。函数定义的语法为:deffunc_name(arg1,arg2,arg3,...,argN):statementreturnvalue根据上面的定义,可以简单描述为:Python中的一个函数有0个或多个参数,有几行语句和一个带有返回值的语句块(注意缩进)(返回值是可选的)。然后我们定义一个比较简单的函数,没有参数,进入ipython交互环境:In[1]:defhello():...:print('Leavemealone,theworld')...:call(execute)这个函数:在[2]:hello()Leavemealone,theworld我们发现hello()函数没有return语句。在Python中,如果没有显式执行return语句,则函数的返回值默认为None。我们说过定义函数有两种形式,另一种形式是使用lambda定义的。使用lambda定义的函数是匿名函数,后面会解释,这里暂时不展示。2.函数参数在定义函数时,我们确定了参数的名称和位置,函数的接口定义就完成了。对于函数的调用者来说,知道如何传递正确的参数以及函数将返回什么值就足够了。函数内部的复杂逻辑被封装了,调用者不需要知道。Python的函数定义很简单,但是很灵活。除了通常定义的强制参数外,还可以使用默认参数、可变参数和关键字参数,这样函数定义的接口不仅可以处理复杂的参数,还可以简化调用者的代码。1.默认参数默认参数使API简洁而不失灵活性。当一个参数有默认值时,调用时如果不传这个参数,就会使用默认值。definc(init,step=1):returninit+step#调用这个函数>>>inc(3)4>>>inc(3,2)5默认参数有个坑,就是非默认参数应该是放在默认参数的前面(否则Python解释器会报语法错误)。允许有多个默认参数,但需要将默认参数放在参数列表的第一侧。defappend(x,lst=[]):returnlst.append(x)这个函数有问题。(函数中的形参是全局变量?lst在append函数中叫做lst,但是在全局范围内,我们不知道lst叫什么名字。)修改后的函数为:defappend(x,lst=None):iflstisNone:lst=[]lst.append(x)returnlst一般来说,默认参数可变时,需要特别注意作用域的问题。我们需要上面的技巧(不可变数据类型是按值传递的,可以被改变的数据类型是按引用传递的。)。当前可变对象有list、dict、set、bytearray。默认参数很好用,但是如果使用不当,也会掉坑里。默认参数有一个很大的坑,如下:#先定义一个函数,传入一个list,加上一个END,然后在我们正常调用的时候returndefadd_end(L=[]):L.append('END')returnL,结果似乎不错:>>>add_end([1,2,3])[1,2,3,'END']>>>add_end(['x','y','z'])['x','y','z','END']当我们使用默认参数调用时,结果也正好在开头:>>>add_end()['END']然而,当我们再次调用add_end(),结果错误:>>>add_end()['END','END']>>>add_end()['END','END','END']原因解释为如下:Python函数定义时,会计算默认参数L的值,即[],因为默认参数L也是一个变量,指向对象[],每次调用函数时,如果改变了L的内容,默认参数的内容也改变了,不再是函数定义时的[]。因此,在定义默认参数时,请记住一件事:默认参数必须指向不变对象!修改上面的例子,我们可以使用None作为不变对象:defadd_end(L=None):ifLisNone:L=[]L.append('END')returnL为什么要设计str和None这样的不变对象呢?因为一旦创建了不变对象,就不能修改对象内部的数据,减少了修改数据带来的错误。另外,由于对象没有变化,在多任务环境下读取对象时不需要加锁,同时读取也没有问题。我们在写程序的时候,如果可以设计一个不变对象,那就尽量把它设计成一个不变对象。2.位置参数我们先写一个计算x^2的函数:defpower(x):returnx*x对于power(x)函数,参数x是一个位置参数。我们在调用power函数的时候,必须传入唯一的参数x:>>>power(5)25>>>power(15)225那么,如果我们要计算x^3呢?然后你可以定义一个power3函数,但是如果你想计算x^4、x^5、x^n怎么办?我们不能定义多个函数,我们可以把power(x)改成power(x,n),用来计算x^n,照你说的写:defpower(x,n):s=1while>0:n=n-1s=s*x返回3。关键字参数可变参数允许我们传入0或任意参数,这些可变参数在函数调用时自动组装成一个元组。关键字参数允许你传入0或者任何包含参数名的参数,这些关键字参数会在函数内部自动组装成一个dict。例子如下:defperson(name,age,**kwargs):print('name:',name,'age:',age,'other:',kwargs)除了必须的参数name和age,函数person也接受关键字参数kwargs。调用该函数时,可以只传入必要的参数:>>>person('LavenLiu',25)name:LavenLiuage:25other:{}也可以传入任意数量的关键字参数:>>>person('LavenLiu',25)name:LavenLiuage:25other:{}>>>person('淘气',25,city='河北')name:Taoqiage:25other:{'city':'河北'}>>>person('James',31,gender='M',job='NBAplayer')name:Jame??sage:31other:{'gender':'M','job':'NBAplayer'}关键字参数有什么用?它可以扩展函数的功能。比如在person函数中,我们保证能收到name和age这两个参数,但是如果调用方愿意提供更多的参数,我们也可以收到。假设您正在执行用户注册功能。除了用户名和年龄是必填项外,其他都是可选的。使用关键字参数定义该函数即可满足注册要求。和可变参数类似,也可以先组装一个dict,然后把dict转成关键字参数:>>>kwargs={'city':'Hebei','job':'Test'}>>>person('Taoqi',25,**kwargs)name:Taoqiage:25other:{'city':'Hebei','job':'Test'}4.位置参数和关键字参数位置参数和关键字参数是函数的概念称呼。在将默认参数与关键字参数组合时很有用。关键字参数必须写在位置参数之后,否则会抛出语法错误。defminus(x,y):returnx-yminus(3,5)#position参数,position参数minus(5,3)#position参数,position参数minus(x=5,y=3)#keyword参数,关键字参数minus(y=3,x=5)#关键字参数,关键字参数参数和关键字参数可以共存,但是关键字参数必须写在position参数之后。5.可变位置参数可变位置参数用*定义,在函数体中,可变位置参数是一个元组。可变位置参数。In[1]:deffn(*args):...:print(args)...:In[2]:fn((1,2,3,4))((1,2,3,4),)In[3]:tup01=(1,2,3,4)In[4]:fn(tup01)((1,2,3,4),)In[5]:fn(*tup01)(1,2,3,4)在python函数中,也可以定义可变参数。可变参数是指传入的参数个数是可变的。In[6]:defcacl(*numbers):...:sum=0...:forninnumbers:...:sum=sum+n*n...:returnsum...:In[7]:nums=[1,2,3]In[8]:cacl(*nums)#这里nums前面不加*是不是有问题?输出[8]:146。可变关键字参数可变关键字参数使用**定义,在函数体内,可变关键字参数是一个字典。variable关键字参数的key均为字符串,符合标识符定义规范。deffn(**kwargs):print(kwargs)dict01={'name':'LavenLiu','age':29}fn(**dict01)#fn(dict01)fn(name='LavenLiu',age=29){'name':'LavenLiu','age':29}{'name':'LavenLiu','age':29}可变位置参数只能作为位置参数调用可变关键字参数只能被调用与调用关键字参数可变位置参数必须在可变关键字参数之前In[18]:deffn(*args,**kwargs):...:print(args)...:print(kwargs)...:In[19]:fn(1,2,3,a=1,b=2)(1,2,3){'a':1,'b':2}In[20]:deffn(*args,x,y):...:print(args)...:print(x,y)...:In[21]:fn(1,2,3,4)----------------------------------------------------------------------------TypeErrorTraceback(mostrecentcallast)in()---->1fn(1,2,3,4)TypeError:fn()missing2requiredkeyword-onlyarguments:'x'and'y'In[22]:fn(1,2,x=3,y=4)(1,2)34变量参数后变量参数不与默认参数一起出现7.参数组合在Python中定义一个函数,可以使用强制参数、默认参数、可变参数和关键字参数,这4种参数可以一起使用,也可以只使用其中的一部分,但请注意参数定义的顺序必须是:必需参数、默认参数、可变参数和关键字参数例如,定义一个包含以上四个参数的函数:>>>deffunc(a,b,c=0,*args,**kwargs):。..print('a=',a,'b=',b,'c=',c,'args=',args,'kwargs=',kwargs)函数调用时,Python解释器自动跟随参数位置和参数名称传入相应的参数。>>>func(1,2)a=1b=2c=0args=()kwargs={}>>>func(1,2,c=3)a=1b=2c=3args=()kwargs={}>>>func(1,2,3,'a','b')a=1b=2c=3args=('a','b')kwargs={}>>>func(1,2,3,'a','b',x=99)a=1b=2c=3args=('a','b')kwargs={'x':99}>>>最神奇的是通过一个元组和dict,我们也可以调用这个函数:>>>args=(1,2,3,4)>>>kwargs={'x':99}>>>func(*args,**kwargs)a=1b=2c=3args=(4,)kwargs={'x':99}所以,对于任何函数,无论其参数如何定义,都可以以func(*args,**kwargs)的形式调用.8.参数解构参数解构发生在调用函数时,定义函数时出现变量参数。参数解构有两种形式,一种是位置参数解构,另一种是关键字参数解构。参数结构的两种形式:位置参数解构,使用星号。解构后的对象是一个可迭代对象,解构后的结果是一个位置参数。关键字参数解构,使用两个星号。解构后的对象是字典,解构后的结果是关键字参数。位置参数解构的一个例子:In[23]:deffn(a,b,c):...:print(a,b,c)...:In[24]:lst=[1,2,3]In[25]:fn(lst[0],lst[1],lst[2])123#也可以用下面的形式调用In[26]:fn(*lst)#这个方法称为参数解构123#*可以把线性结构解包成位置参数lst=[1,2,3,4]fn(*lst)#->fn(lst[0],lst[1],lst[2],lst[3])TypeError:fn()takes3positionalargumentsbut4weregiven#这里报错。本来这个函数只能接收3个位置参数,而lst有四个元素。参数解构后变成4个参数,所以报错。.接下来看字典解构的例子:In[27]:d={'a':1,'b':2,'c':3}In[28]:fn(**d)123#**是将字典解构为关键字参数参数解构发生在函数调用时。解构时,线性结构的解构是位置参数,字典的解构是关键字参数。传参顺序:位置参数,线性结构解构;关键字参数,字典解构。除非你真的知道你在做什么,否则尽可能少地使用这两种解构。In[29]:deffn(a,b,c,d):...:print(a,b,c,d)...:In[30]:fn(0,*[2],c=1,**{'d':3})02139。Python3引入的参数槽(keyword-onlyparameter)。deffn(a,b,c):print(a,b,c)fn(a=1,b=2,c=3)如果要强制传入参数为关键字参数:deffn(*,a,b,c):print(a,b,c)>>>fn(1,2,3)Traceback(mostrecentcallast):File"",line1,infn(1,2,3)TypeError:fn()takes0positionalargumentsbut3weregiven>复制代码>>fn(a=1,b=2,c=3)123#*后面的参数必须以关键字参数的形式传递,称为参数槽。参数槽通常与默认参数一起使用。>>>deffn(a,b,*,x,y):print(a,b)print(x,y)>>>fn(1,2,3,4)Traceback(mostrecentcallast):文件"",line1,infn(1,2,3,4)TypeError:fn()接受2个位置参数,但重新给出了4个>>>fn(1,2,x=3,y=4)1234>>>fn(1,2,**{'x':3,'y':4})1234deffn(a,b,*):print(a,b)deffn(a,b,*):..print(a,b)File"",line1SyntaxError:namedargumentsmustfollowbare*几个例子:deffn01(*,x=1,y=5):print(x)print(y)>>>fn01()15deffn02(x=1,*,y):print(x)print(y)>>>fn02(y=3)13参数槽的坑:*后面一定要有参数。当非命名参数有默认值时,命名参数可以没有默认值。默认参数应该在每个参数的底部。使用参数槽时,不能使用可变位置参数,可变键参数必须放在命名参数之后。三、高级用法1、递归函数在函数内部,可以调用其他函数。如果函数在内部调用自身,则该函数是递归的。deffact(n):ifn==1:return1returnn*fact(n-1)使用递归函数的优点是逻辑简单明了,缺点是调用太深会导致栈溢出。针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归其实就相当于循环,没有循环语句的编程语言只能通过尾递归来实现循环。2.匿名函数lambdapython使用lambda创建匿名函数。lambda只是一个表达式,函数体比def要简单的多。lambda的主体是一个表达式,而不是代码块。lambda表达式只能封装有限的逻辑。lambda函数有自己的命名空间,不能访问自己的参数列表之外或全局命名空间中的参数。虽然看起来lambda函数只能写一行,但并不等同于C或C++的内联函数。后者的目的是通过在调用小函数时不占用栈内存来提高运行效率。fib=lambdan,x=0,y=1:??xifnotnelsefib(n-1,y,x+y)print(fib(20))3.Python函数中的多态性一个操作的意义取决于被操作对象的类型:deftimes(x,y):returnx*y>>>times(2,4)>>>8times('Python',4)#传递了一个不同的数据类型'PythonPythonPythonPython'。4.总结Python的功能非常灵活的参数形式,既可以实现简单的调用,也可以传入非常复杂的参数。默认参数必须使用不可变对象。如果是变量对象,运行就会出现逻辑错误!注意定义可变参数和关键字参数的语法:*args是一个可变参数,args接收一个元组;**kwargs是一个关键字参数,kwargs接收一个dict。以及调用函数时如何传入可变参数和关键字参数的语法:可以直接传入可变参数:func(1,2,3),也可以先组装一个list或tuple,再传入*参数:函数(*(1、2、3));关键字参数可以直接传入:func(a=1,b=2),也可以先组装dict,再通过kwargs传入:func({'a':1,'b':2}).使用*args和**kwargs是Python的习惯用法。当然也可以用其他的参数名,但是要用成语。
