python封装函数语法_Python基础--第6章 函数操作——功能化程序片段的封装

本章讲的是函数操作。在了解函数操作之前,先来介绍一种编程思想的方法,面向过程思想。

面向过程思想是一种以过程为中心的编程思想。是最为实际的一种思考方式。面向过程思想的大概意思是先分析出解决问题所需要的步骤,接着编写函数来实现每一步的功能,最后在对每个函数依次调用。

在面向过程开发中,最重要的是模块化的思想。函数在模块化思想中起到了至关重要的作用。它可以将程序裁分成一个个程序片段,来独立实现某个完整的功能,进而实现模块化。

函数(function)是组织好的、可重复使用的、具有一定功能的代码段。它能提高应用的模块性和代码的重复利用率。在Python中,用户可以自定义函数。同时Python还提供了很多内建函数,比如print、input等方便用户调用。

6.1 函数的基本概念

Python中有固定的格式来定义函数,函数也有具体的组成部分(函数名、函数体、参数、返回值)。从分类上又可以分为内置函数、自定义函数等。为了实现不同的编程需求,它们在使用中会有各种各样的规则,同时还要配合这作用域的限制,来完成整个的功能流程。具体介绍如下:

6.1.1 函数的定义

定义函数使用关键字def,后接函数名和放在圆括号( )中的可选参数列表,函数内容以冒号起始并且缩进。一般格式如下

def函数名(参数1, 参数2,……, 参数N):

例:

defhello(strname): #定义一个函数hello

print (strname) #函数的内容为一句代码,实现将指定内容输出

单独的函数是运行不起来的。需要对其调用才可以执行。调用的时候,直接使用函数名称即可。例:

hello(“Ilove Python!”) #调用函数,这时候屏幕会输出:I love Python!

6.1.2 函数的组成部分

Python中的函数有固定的组成部分。这些是构成一个普通函数的基本要素。在后面还会讲解不同类型的函数,其变化也都是在基本组成部分上变化而来的。

1. 函数的组成

函数一共可以分为四个组成部分:

l 函数名:def后面的名字,例如6.1.1例中的hello;

l 函数参数:函数名后面的变量,例如6.1.1例中的strname;

l 函数体:函数名的下一行代码,起始需要缩进,例如6.1.1例中的print (strname);

l 返回值:函数执行完的返回内容,用return开始。当没有返回值时可以不写。例如6.1.1例中没有返回值。

对于一般类型的函数来讲,函数名和函数体是必须有的,函数的参数和返回值是可选的。

2. 文档字符串

在函数体中,一般第一行会放置一个多行字符串,用来说明函数的功能及调用方法。这个字符串就是函数的文档字符串,或称为docstring 。我们可以使用print(function.__doc__)输出文档。例如:

defgetrecoder(): #定义一个函数getrecoder

'''该函数是返回用户名字和年龄'''

name = 'Gary'

age = 32

return name,age #返回name和age的值

print(getrecoder.__doc__) #返回文档字符串。输出:该函数是返回用户名字和年龄

例子中,在函数体的第一句就放置了一个文档字符串。该字符串用来对函数进行描述。实际应用中,文档字符串主要用于描述一些关于函数的信息,让用户交互地浏览和输出。建议养成在代码中添加文档字符串的好习惯。

6.1.3 函数的参数:形参与实参

6.1.2中谈到的参数都是属于形参。形参是从函数的角度来说的。如果从调用的角度来说,在调用时传入的参数就是实参。

举例6.1.1例子中的调用代码hello(“I love Python!”) ,其中 “I love Python!”就是传入hello函数中的实参。它代表实际的参数值。在函数执行时,在函数hello的函数体中,实现的功能是将参数strname打印出来。参数strname的值就是“I love Python!”,于是屏幕就会输出“I love Python!”。

6.1.4 函数的返回值

前面介绍过,函数不需要返回值时,可以什么都不做。在需要有返回值时,就要使用return语句将具体的值返回回去。使用return语句可以一次返回多个值,调用函数的代码使用等号来接收。可以使用与返回值对应的变量个数来接收、也可以使用一个元组来接收。例如:

defgetrecoder(): #定义一个函数getrecoder

name = 'Gary'

age = 32

return name,age #返回name和age的值

myname,myage= getrecoder() #在调用的时候,使用与返回值对应的两个值来接收

print(myname,myage) #将返回值打印出来,输出:Gary 32

person= getrecoder() #在调用的时候,使用与返回值对应的一个值来接收

print(person) #将返回值打印出来,输出:('Gary',32)

在某种情况下,有可能是需要用到返回值的一个,其他的想忽略掉。可以使用下划线(_)来接收对应返回值。例如:

personname,_= getrecoder() #在调用的时候,使用_来接收不需要的返回值

print(personname)

6.1.5 函数的属性

在6.1.2中的“2.文档字符串”中讲到过函数的文档字符串部分。它是存放在函数的__doc__属性中。这个属性是可更改的,可以修改函数的__doc__属性值来为函数指定新的文档字符串。例如:

defgetrecoder(): #定义一个函数getrecoder

'''该函数是返回用户名字和年龄'''

name = 'Gary'

age = 32

return name,age #返回name和age的值

print(getrecoder.__doc__) #返回文档字符串。输出:该函数是返回用户名字和年龄

getrecoder.__doc__= "新文档字符串" #修改该函数__doc__属性值

print(getrecoder.__doc__) #打印文档字符串。输出:新文档字符串

类似这样的功能,在定义函数的同时,还可以为函数添加自定义属性。每个函数都有个默认的__dict__字典,为函数添加的属性就会放在这个字典里面。例如:

defrecoder(strname,*,age): #定义一个函数recoder

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

print(recoder.__dict__) #函数recoder没有任何属性,__dict__为空。输出:{}

recoder.attr="person" #为函数recoder添加属性attr等于person

print(recoder.__dict__) #再次观察__dict__的值。输出:{'attr': 'person'}

print(recoder.attr) #可以直接将attr的值取出来。输出:person

直接在函数名后面加个点就可以加上任意需要的属性;获取该属性时也同样是在该函数名后面加个点,再跟上函数的具体属性名。这种方式可以在使用函数时传递更多的信息。

6.1.6 函数的本质

函数本质也是一个对象,支持被调用的函数都会继承了可调用的方法call,可以使用函数callable来检查某函数是否可以被调用。例如:

defrecoder(strname,*,age): #定义一个函数recoder

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

print(callable(recoder) ) #测试函数recoder是否可以被调用。输出:True

例子中使用了callable函数判断recoder是否可以被调用。结果返回True,表明函数recoder是可以被调用的。

6.1.7 函数的分类

从用户角度看,函数可以分为内置函数,与自定义函数。所谓内置函数就是Python内部自带的函数。自定义函数为用户自己写的函数。

从功能上又可以分为生成器函数、一般函数。

从形式的角度来看,Python中的函数主要有五种形式:

l 普通的函数,使用def来定义;

l 匿名函数,使用lambda关键字;

l 可迭代函数,属于一种特殊的内置函数;

l 递归函数,自己调用自己的函数;

l 偏函数,使用partial关键字。

本篇默认讲的都是普通函数,在下文会对匿名函数、可迭代函数、递归函数和偏函数单独介绍。

6.2 函数中参数的定义与调用

函数只有在调用的过程中才会运行,与函数调用息息相关的就是参数,明白了形参和实参之后,就可以系统的了解函数参数的定义形式以及具体的调用规则了。

6.2.1 函数参数的定义方法与调用形式

函数参数的定义有四种方式,下面来一一介绍:

1. 列表方式:fun(参数1, 参数2, …)

这是最常见的定义方式,一个函数可以定义任意个参数,每个参数间用逗号分割。例如:

defrecoder(strname,age): #定义一个函数recoder

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

2. 函数的调用:按照形参顺序依次传入

单独的函数是运行不起来的。需要对其调用才可以执行。调用的时候,直接使用函数名称,并且还要提供个数相等的实参。默认的情况下实参顺序需要与形参一一对应。例:

recoder("Gary", "32") #调用函数,这时候屏幕会输出:姓名:Gary 年纪:32

上面代码中recoder函数有两个参数strname,age。调用函数recoder的时候,必须也要传入两个参数。在没有任何指定的情况下,这这两个实参还需要与形参的顺序一一对应。

3. 函数的调用:传入指定形参

当然也可以指定具体某个形参来为其赋值。在指定的具体形参的情况下,传入的参数就可以与形参的顺序无关了。例如上面的调用语句还可以写成这样:

recoder(age=32,strname="Gary ") # 通过指定形参的方式调用函数,输出:姓名:Gary 年纪:32

这次传入实参顺序不一样了,并且age的参数的类型也不是字符串了,而是一个整型。因为Python中的变量在定义时是不需要指定类型的。只有在调用时,系统才会根据传入的实参(32)定义函数(recoder)中的形参(age)的类型(int)。所以在该例子中,可以为一个函数的形参传入两个不同类型的实参。

这种方式的通用性也是有限制的。如果在函数体里做了某个特殊类型的操作,那就必须传入这个类型的实参。例如,改写上面的recoder函数如下:

defrecoder(strname,age): #定义一个函数recoder

return age+1 #将age+1返回

在函数体里将形参age进行加一操作,并且使用return关键之返回该值。这很显然是将age当成了数值类型来处理。这时如果传入字符串就会报错,因为字符串不能与一个整型常量(1)相加。这种错误在函数调用时,会带来许多不必要的麻烦。为了解决这个问题,可以提前对参数类型进行检查。当传入错误类型时,被调函数能够及时发现。具体的方法见6.2.2小节的内容。

4. 星号形参对函数调用的影响

另外,在参数的列表中还可以直接使用星号(*),代表调用函数时,在星号的后面的参数都必须要指定参数名称,例如:

defrecoder(strname,*,age): #定义一个函数recoder,要求形参age必须被指定

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

recoder("Gary",age= 32) #调用函数,并指定形参age

recoder("Gary",32) #错误写法。因为没有指定形参age

例子中,函数recoder的形参使用了星号,星号后面为形参age。这表明该函数被调用时age必须被指定。接下来又给出了两种调用方法,其中第一种指定了形参age。而第二种为错误方法,因为没有指定形参age。

5. 带默认实参的列表方式:fun(参数1,参数2=值2,…)

这种方式是对第一种的改进,为某些参数提供了默认值。在调用的时候,被提供默认值的形参可以不需要有实参与其对应。没有传入实参的形参,自动会取默认值为其初始化。例如:

defrecoder(strname,age=32): #定义一个函数recoder

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

调用的时候,传入一个或两个参数就可以,例如:

recoder("Gary", "32") #调用函数,传入两个参数。输出:姓名:Gary 年纪:32

recoder("Gary") #调用函数,传入一个参数。输出:姓名:Gary 年纪:32

这里要注意的地方是,有默认值的形参必须要放在没有默认值的形参后面。否则会报错。例如下面的是错误的写法:

defrecoder(age=32,strname): #错误的写法

6. 通过元组或列表的解包参数的方式:fun(*参数)

这种写法只有一个形参,它允许在调用的时候,传入任意多的实参。当被调用时,形参会被定义为一个元组。传入的实参都是这个元组类型的形参的元素。在函数体中,可以通过访问形参中的元素来获取传入的实参。例如:

defrecoder (*person): #定义一个函数,形参person的类型为元组

print('姓名:', person[0],'年纪:', person[1]) #函数的内容为一句代码,实现将指定内容输出

调用的时候,传入两个参数,例如:

recoder("Gary", "32") #调用函数,传入两个参数。输出:姓名:Gary 年纪:32

上面代码中传入实参的个数为3个、4个等也会正常执行。如果传入一个实参会有问题。因为在函数recoder的函数体里,会获取形参的第二个元素(person[1])。如果只传入一个实参,相当于person只有一个元素,获取其第二个元素自然会失败。这是需要注意的地方。

另外,还有两点要注意的地方:

l 用这种方式就无法通过指定形参名称来传入实参了。而且传入的实参顺序与形参内部元素的顺序必须一一对应;

l 因为接收参数的类型是元组。用这种方式传值后,不能对形参内容进行修改。

最后再介绍一种调用的方法,可以支持将一个列表或元组当作实参传入。具体做法就是在列表或元组的前面加上一个星号传入。例如:

Mylist= ["Gary", "32"] #定义一个list

recoder(*Mylist) #调用函数,传入list作为实参。输出:姓名:Gary 年纪:32

到这可以发现函数recoder的参数为*person,是将接收的参数当作元组,而调用时传入了*Mylist,是一个列表的类型前面加个星。那么,如果把person前面的*与Mylist前面的*同时去掉是不是也可以呢?它与加个*的传递有什么区别呢? 读者可以先自己想一下,在6.2.3部分会详细讲解。

7. 通过字典的解包参数的方式:fun(**参数)

前面的第3种方式中,不限制实参个数是比较方便的。但是要求获取的实参与传入的顺序一一对应,这就是不方便的地方。在这个基础上进行改进:传入的实参同时,为其定义个形参。这样在函数里就可以通过指定具体形参名称来获取实参了。这种方式也是通过一个形参来接收,将该形参当成字典。这样传入的实参和对应的名字就可以放到这个字典里。形参为字典中元素的key,实参为字典中元素的value。取值的时候,直接通过字典里的key找到value即可。例如:

defrecoder (**person): #定义一个函数,形参person的类型为字典

print('姓名:', person['name'],'年纪:', person['age']) #函数的内容为一句代码,实现将指定内容输出

调用的时候,在传入实参的同时也指定了形参名称,例如:

recoder(age=32,name="Gary ") # 指定形参名称调用函数,输出:姓名:Gary 年纪:32

如果使用了这种写法,就必须为形参指定名称,不然系统会报错误。例如:

recoder("Gary", "32") #错误的写法

类似第三种方法,该方法中可以将一个字典当作实参传入。具体方法是在调用函数时,传入字典变量,并在前加两个星号。例:

Mydic= {"name":"Gary", "age":"32"} #定义一个字典

recoder(**Mydic) #调用函数,传入list作为实参。输出:姓名:Gary 年纪:32

8. 总结:混合使用

其实上面的4种方式也可以混合使用。因为第二种方式本来就包含了第一种方式,这里不再重复。这里主要介绍下其他其中方法混合使用的情况。

(1)字典和元组的解包参数同时作为形参来接收实参:

具体做法为:定义两个形参,第一个前有一个星号,用来接收实参并转为元组;第二个前有两个星号,用来接收实参并转为字典。例如

defrecoder(*person1,**person2): #定义一个函数recoder,包括两个形参

if len(person1)!=0: #如果元组的形参接收到内容,就打印

print ('姓名:',person1[0],'年纪:',person1[1])

if len(person2)!=0: #如果字典的形参接收到内容,就打印

print ('姓名:',person2["name"],'年纪:',person2["age"])

调用时候,可以放入的实参可以指定形参也可以不指定。例如:

recoder("Gary",32) #调用函数recoder,传入不指定形参的实参,由person1接收

recoder(age=32,name="Gary") #调用函数recoder,传入指定形参的实参,由person2接收

还可以将指定形参的实参与不指定形参的实参同时放入函数来调用。例如:

recoder("Gary",32,age=32,name="Gary")#传入指定形参的实参与不指定形参的实参,person1、person2同时接收

上面的这种写法必须是不指定形参的实参在前,指定形参的实参在后,不然会报错。例如:

recoder(age=32,name="Gary","Gary",32)#错误写法

(2)字典或元组解包参数与单个形参的混合使用:

直接将字典或元组的解包参数与单个形参放在一起即可。但是放置的先后顺序会影响到调用时的写法。先看看元组解包参数在前单个形参在后的写法:

defrecoder(*person1, ttt): #定义一个函数recoder,两个形参

if len(person1)!=0:

print ('姓名:',person1[0],'年纪:', ttt)

recoder("Gary",ttt=32) #调用时需要指定后面的单个形参,输出:姓名:Gary 年纪:32

元组解包参数在前单个形参在后时,调用语句必须得指定形参名称。

如果形参在前,元组解包参数在后的时候,需要如下的写法;

defrecoder(ttt,*person1): #定义一个函数recoder

if len(person1)!=0:

print ('姓名:',ttt,'年纪:',person1[0])

recoder("Gary",32) #调用时不需要指定形参,输出:姓名:Gary 年纪:32

函数的前单个形参在前元组解包参数在后时,调用语句不需要形参名称。当然传入实参时指定形参名称也是可以的。

(3)字典解包参数、元组的解包参数、单个形参放一起使用:

当字典、元组的解包参数与单个形参放在一起时,必须保证字典的解包参数放在最后。例如:

defrecoder(ttt,*person1,**arg): #定义一个函数recoder

if len(person1)!=0:

print ('姓名:',ttt,'年纪:',person1[0])

recoder("Gary",32) #调用时不需要指定形参,输出:姓名:Gary 年纪:32

当字典解包参数、元组的解包参数、单个形参放一起,只需要注意下三者的顺序即可,对于调用的规则,还是与前面(1)(2)点一致。

如果将第一个形参(ttt)与第二个形参(*person1)颠倒一下,也是可以的。例如

defrecoder(*person1, ttt,**arg): #定义一个函数recoder

if len(person1)!=0:

print ('姓名:',ttt,'年纪:',person1[0])

recoder("Gary",ttt=32) #调用时不需要指定形参,输出:姓名:Gary 年纪:32

按照前面(2)的规则当元组的解包参数在单个形参前面时,单个形参需要被指定。所以调用的时候第二个实参指定了形参ttt。

如果想下面的写法就是错误的:

defrecoder(*person1, **arg, ttt): #错误,arg没有在最后

if len(person1)!=0:

print ('姓名:',ttt,'年纪:',person1[0])

上例中字典的解包参数在中间,没有在最后,所以错误。这是个必须要注意的地方,即便形参中带有默认实参,也需要放到字典的解包参数前面。例如:

defrecoder(*person1,ttt=9,**arg): #ttt给定了一个默认值,但是它也得放在arg前面。

if len(person1)!=0:

print ('姓名:',ttt,'年纪:',person1[0])

上例子中函数recoder的形参ttt给定了一个默认值,一般情况下这种带默认值的形参需要放在最后面(这里的先后指的是从左到右的顺)。但是有了arg的存在,就需要放在arg前面,其他形参的后面。

那么在Python中,函数的实参与形参是如何传递的呢?下面就来看看具体规则。

6.2.2 函数调用中的参数的检查

在函数调用中,Python本身的语法没有对传入参数类型的检查。这种做法增加了代码的灵活度,同时也提升了程序出错的概率。而在工业生产环境下应用的程序,更需要的是代码的健壮性。这种情况下,需要手动对函数的参数进行检查,来提升自有代码的健壮性。具体的方法是通过使用isinstance函数来对参数进行检查。

isinstance函数的作用是检查某个变量是属于某个类型。具体定义如下:

isinstance(obj,class_or_tuple)

l 第一个参数obj表示传入待检查的具体对象;

l 第二个参数class_or_tuple为一个tuple的类型序列或是class;

确切的说,该函数的功能是检查obj是否为class_or_tuple的一个实例。关于类和实例的的介绍可以在第9章学到。

在使用的过程中,直接在函数体的刚开始部分使用isinstance判断一下参数即可。isinstance的第一参数传入带判断的形参;isinstance的第二个参数放的就是合法的类型。例如:

defrecoder(strname,age): #定义一个函数recoder

if not isinstance(age, (int, str)): #对参数类型进行检查,合法的类型为int和str

raise TypeError('bad operand type') #如果类型错误,使用raise函数进行报错

print ('姓名:',strname,'年纪:',age) #函数的内容为一句代码,实现将指定内容输出

recoder("Gary",age= 32.2) #掉用时传入了age为float类型

上面的代码中允许age可以传入的类型为整型和字符串类型,但是传入了浮点型。所以报错。这里使用了raise函数进行了报错提示并退出函数。关于raise类似的功能及介绍可以参考第7章内容。

6.2.3 函数调用中的参数传递及影响

在实参与形参的传递过程中,分为两种情况:传值和传引用。

l 对于不可变对象,函数调用时,是传值,意味着函数体里可以使用实参的值,但不能改变实参;

l 对于可变对象,函数调用时,是传递引用。意味着在函数体里还可以对实参进行修改。

具体情况举例如下:

1. 不可变对象

不可变对象的传值是指:当传入的实参指向一个常量,或是一个元组等不允许修改的对象时,就把该对象的值传递给形参。例如:

deffun(arg): #定义一个函数

arg = 5 #在函数体里通过为形参赋值的方式改变形参

x= 1 #定义一个变量x,让它等于1

fun(x) #输出将x当作实参传入函数fun

print(x) #打印x,因为x的值(1)为不可变对象,所以其值不变。输出: 1

上面这段代码把x作为参数传递给函数,这时x和arg都指向内存中值为1的对象。

在函数体中,执行了arg = 5。由于int对象不可改变,于是创建一个新的int对象(值为5)并且令arg指向它。而x仍然指向原来的值为1的int对象,所以函数没有改变x变量。

2. 可变对象

可变对象的传递引用是指:当传入的实参指向一个list,或是一个字典等允许修改的对象时,就把该对象的值传递给形参。例如:

deffun(arg): #定义一个函数,形参为一个列表

arg.append(3) #在函数体里通过为形参添加一个元素的方式改变形参

x= [1, 2] #定义一个列表x,

fun(x) #将x传入函数

print(x) #执行完函数fun,列表x反生了变化,输出:[1, 2, 3]

这段代码同样把x传递给函数fun。x和arg都会指向同一个list类型的对象。

函数体中使用列表的append方法为其在其末尾添加了一个元素。因为列表对象是可以改变的,所以列表对象的内容发生了改变。由于x和arg指向的是同一个list对象,所以变量x的内容也发生了改变。

3. 综合分析

在6.2.1中的“6通过元组或列表的解包参数的方式:fun(*参数)”部分,抛出了一个问题,使用解包参数与列表传递有什么不同。看下面的例子:

Mylist=["Gary", "32"]

defrecoder (person): #定义一个函数,形参为person,未指定类型

person[0] = 'sss' #修改person中元素的值

print('姓名:',person[0],'年纪:', person[1]) #将指定内容输出

recoder(Mylist) #调用recoder,输出:姓名: sss 年纪: 32

print(Mylist) #将Mylist打印,输出:['sss', '32']

这个例子是个可变对象的传值,Mylist的值在调用完recoder函数发生了变化。接下来为函数recoder的参数person与调用是传入的Mylist前面同时加上*,看下使用解包参数传值的情况:

Mylist=["Gary", "32"]

defrecoder (*person):

person[0] = 'sss' ##修改person中元素的值

print('姓名:',person[0],'年纪:', person[1]) #将指定内容输出

recoder(*Mylist) # 调用函数,传入*Mylist。会报错误,提示函数recoder中不可以修改person的值

print(Mylist)

上例中,使用解包参数的方式进行值传递,并且在recoder函数中对person进行了修改,这是错误的写法,因为person是元组类型,不支持修改。所以在调用时会报错误。

这两个例子就可以所名解包参数和直接列表传值的区别。使用解包参数的函数不能对参数修改,而使用列表传值的函数是可以对参数修改的。

6.3 匿名函数与可迭代函数

匿名函数一般适用于单行代码函数。它的存在会把某一简单功能的代码封装起来,让整体代码更规整一些。一般只调用一次就不用了,所以名字也省略了,于是变成了匿名函数。

6.3.1 匿名函数与可迭代函数的介绍

匿名函数是以关键字lambda开始的,它的写法如下:

Lambda参数1,参数2…:表达式

上面的写法中,表达式的内容只能是一句话的函数,而且不能有return关键字存在。

例如:

r= lambda x,y:x*y #定义一个匿名函数实现x与y相乘

print(r(2,3)) #传入2和3,并把它们打印出来。输出:6

lambda表达式可以在任何地方使用,它相当于一个可以传入参数的单一表达式。例如在一般函数里使用:

defsum_fun(n): #定义个函数

return lambda x: x+n #返回一个匿名函数

f= sum_fun(15) #得到一个匿名函数,函数体为x+15

print(f(5)) #像匿名函数里传入5。输出:20

在上面的例子中,在一个一般函数里返回匿名函数,并为其指定了一个被加数。在调用的过程中,只需要放入另一个加数,便实现了两个数相加。

匿名函数本质与函数是没什么两样的。在实际应用中,匿名函数常常会与可迭代函数配合使用。可迭代函数就是一种有循环迭代功能的内置函数,包括reduce、map、filter等。在每个可迭代函数中,都需要指定一个处理函数.处理函数习惯上就会使用匿名函数,当然使用普通函数也是有效的。下面就来一一介绍。

6.3.2 匿名函数与reduce函数组合

匿名函数常常来与reduce函数组合使用。reduce函数的功能是依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。其定义如下:

reduce(function,sequence, [initial])

l 第一个参数function表示所要回调的函数;

l 第二个参数sequence为一个序列类型的数据;

l 第三个参数可选,是一个初始值。

该函数本质上也可以算作是一个内嵌循环的函数。reduce函数与匿名函数的结合使用,能够以更为简洁的代码实现较复杂的循环计算功能。例如,下面使用reduce函数与匿名函数的结合写法,来实现一个求1到100的和:

fromfunctools import reduce #导入reduce函数

print(reduce(lambda x,y:x + y,range(1,101) ) ) #第一个参数是个匿名函数实现两个数加和,输出:5050

函数里面通过匿名函数实现了两个数的加和,然后使用range函数得到一个1到100的列表。依次取出列表里的值,将它们加在一起。

reduce函数一般用于归并性任务。

6.3.3 匿名函数与map函数组合

类似reduce函数,匿名函数还可以与map函数组合。map函数的功能是:循环所传入的序列类型中的元素,依次调用所指定的函数里。具体定义:

map(function,sequence[, sequence, ...])

l 第一个参数function表示所要回调的函数;

l 第二个参数sequence为一个或多个序列类型的数据;

该函数返回值为一个map对象。在使用时,还得用list或tuple等函数进行转化。

1. map函数处理一个序列数据

当map后面直接跟一个序列数据时,直接取该序列数据中的元素,依次调入前面的函数即可。例如:

t= map(lambda x: x ** 2,[1, 2, 3, 4, 5] ) #使用map函数,将列表[1,2,3,4,5]的元素平方。返回值赋给t

print(list(t)) #将t转成列表类型,并打印。输出:[1, 4, 9, 16, 25]

例子中,map函数会将传入的列表[1, 2, 3, 4, 5]中的每个元素传入匿名函数里,进行平方运算得出的值会放入新的map对象中,最后将整个map对象赋给变量t。通过list函数对t进行类型转换,生成新的列表。在新的列表里,每个元素都为原来列表元素平方后的结果。

2. map函数处理多个序列数据

当map后面直接跟多个序列数据时,所提供的处理函数的参数要与序列数据的个数相同。运行时,map内部会同时取每个序列数据中的元素,一起放到所提供的处理函数中。直到循环遍历完最短的那个序列。例如:

t= map(lambda x,y: x+y,[1, 2, 3, 4, 5],[1, 2, 4, 5] )#使用map将2个列表[1,2,3,4,5]的元素加和。返回值赋给t

print(list(t)) #将t转成列表类型,并打印。输出:[2, 4, 7, 9]

该例子是对两个序列中的元素依次求和。生成的新列表中的元素分别为两个原序列中对应位置的加和。

第一个序列长度是5,第二个序列长度为4。两个序列长度不相等,循环会以最小长度对所有序列进行截取。于是生成的新列表中,也只有四个元素。

map函数一般用于映射性任务。

6.3.4 匿名函数与filter函数组合

filter函数的功能是对指定序列进行过滤。filter的处理函数只接受一个参数的输入,返回值是布尔型。处理函数输出为False的序列元素将会被过滤掉,只留下返回为True的元素。定义如下:

filter(functionor None, sequence)

l 第一个参数function表示所要回调的函数,返回布尔型。意味着某元素是否要留下;

l 第二个参数sequence为一个或多个序列类型的数据;

整个函数的返回值是一个filter类型,需要转成列表或元组等序列才可以使用。例如:

t=filter(lambdax:x%2==0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) #过滤一个列表中为偶数的元素

print(list(t)) #转成列表,并打印。输出[2, 4, 6, 8, 10]

例子中实现了通过fliter来过滤数组中偶数的元素。如果fiter的处理函数为None,则会返回结果和sequence参数相同。例如:

t=filter(None,[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) #过滤一个列表中为偶数的元素

print(list(t)) #转成列表,并打印。输出[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

当fliter的处理函数为None时,返回的元素与原序列的一样。从功能上说没有什么意义;从代码框架的角度会更有扩展性。当处理函数的输入是个变量时,就可以把filter函数的调用部分代码固定下来。为处理函数变量赋值不同的过滤函数,来实现不同的处理功能。

fliter函数一般用于过滤性任务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值