函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。能提高应用的模块性,和代码的重复利用率。
定义函数
定义函数使用关键字def,后接函数名,再后接放在圆括号()中的可选参数列表,最后是冒号。
函数的组成:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号 : 起始,并且缩进。
return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
函数的参数
形参,是从函数的角度来说的。实参,是从调用的角度来说的。在调用时传入的参数就是实参。
传递实参
向函数传递实参的方式很多,可使用位置实参,这要求实参的顺序与形参的顺序相同;也可使用关键字实参,其中每个实参都由变量名和值组成;还可使用列表和字典。
位置实参
你调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式被称为位置实参。
位置实参的顺序很重要:使用位置实参来调用函数时,如果实参的顺序不正确,结果可能出乎意料。
关键字实参
关键字实参是传递给函数的名称 — 值对。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet(animal_type='hamster', pet_name='harry')
注意 使用关键字实参时,务必准确地指定函数定义中的形参名。
默认参数
编写函数时,可给每个形参指定默认值。调用函数时,如果没有传递参数,则会使用默认参数。
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物的信息"""
print("\nI have a " + animal_type + ".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet(pet_name='willie') #调用函数时使用默认值
注意 使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的实参。这让Python依然能够正确地解读位置实参。
不定长参数
通过元组或列表的解包参数的方式,定义参数与调用方式:fun(*参数)
这种定义方式只有一个形参,当被调用时,形参会被定义为一个元组。传入的实参都是这个元组类型的形参的元素。在函数体中,可以通过访问形参中的元素来获取传入的实参。
(1)函数的调用:传入任意多的实参
def recoder(*person): #定义一个函数,形参person的类型为元组
print('姓名:', person[0],'年纪:', person[1] ) #函数的内容为一句代码,实现将指定内容输出
recoder("Gary", "32") #调用函数,传入两个参数。输出“姓名:Gary年纪:32”
注意:
这种方式无法通过指定形参名称来传入实参,而且传入的实参顺序与形参内部元素的顺必须一一对应;
因为接收参数的类型是元组,所以,用这种方式传值后不能对形参内容进行修改。
( 2)函数的调用:传入列表或元组
Mylist = ["Gary", "32"]
#调用时传入的*Mylist是一个列表的类型前面加个星号。
recoder (*Mylist) #调用函数, 传入list作为实参。输出:姓名:Gary年纪:32
通过字典的解包参数方式, 定义参数与调用方式:fun(**参数)
这种方式是将形参当成一个字典类型变量来接收实参。这样传入的实参和对应的名字就可以放到这个字典里。形参为字典中元素的key, 实参为字典中元素的value。
def recoder(**person): #定义一个函数,形参person的类型为元组
print('姓名:', person['name'],'年纪:', person['age'] )
recoder(age=32 , name ="Gary") #指定形参名称调用函数。输出“姓名:Gary年纪:32”
Mydic = {"name":"Gary","age":32}
recoder(**Mydic) #调用时, 还可以直接将字典传入。
函数的属性
文档字符串
在函数体中,常会在开始位置放置一个行字符串,用来说明函数的功能及调用方法。这个字符串叫作函数的文档字符串(docstring),可以使用代码print(函数名._doc_)将其输出。而_doc_则为函数的属性,并且这个属性可修改。
def getrecoder(): #定义一个函数
'''该函数是返回用户名字和年龄'''
name =’ Gary ’
age = 32
return name, age
print(getrecoder._doc_) #输出文档字符串:该函数是返回用户名字和年龄
getrecoder._doc_="新文档字符串" #修改函数的_doc_属性
print(getrecoder . _doc_) #打印文档字符串,输出:新文档字符串
类似这样的功能,在定义函数的同时,还可以为函数添加自定义属性。每个函数都有个默认的字典_dict_,用于放置函数的属性。
def recoder(strname,*,age): #定义一个函数
print('姓名:',strname,'年纪:',age) #函数的内容
print(recoder._dict_) #函数没有任何属性,_dict_为空,输出:{}
recoder.attr="person" #为函数添加属性attr
print(recoder._dict_) #输出:{'attr':'person'}
print(recoder.attr) #直接取出属性值,输出:person
直接在函数名后面加个点,就可以在后面直接添加属性;获取该属性时,也同样是在该函名后面加个点,再跟上函数的具体属性名。这种方式可以在使用函数时传递更多的信息。
函数的本质
函数的本质时对象,可以被调用的函数,都会继承可调用的方法call,使用函数callable可以检查函数是否被调用。
def recoder(strname,*,age): #定义一个函数
print('姓名:',strname,'年纪:',age) #函数的内容
print(callable(recoder)) #函数是否被调用:输出:True
函数的分类
从用户角度
内置函数:Python内部自带的函数
自定义函数:用户自己编写的函数
从功能角度
生成器函数
一般函数
从形式角度
普通函数:使用def定义
匿名函数:使用lambda关键字来定义
可迭代函数:一种循环迭代功能的内置函数
递归函数
偏函数:使用partial关键字定义
在函数调用中检查参数
在函数调用中,默认是不检查参数的类型的,通过使用isinstance函数可以对参数进行检查。
isinstance(obj , class_or_tuple)
第一个参数obj:传入待检查的具体对象。
第二个参数class_or_tuple:待检查的具体类型, 可以是个类或者元组。
确切的说, 该函数的功能是检查obj是否为class_or_tuple的一个实例。
在使用的过程中, 直接在函数体的开始部分使用isinstance判断一下参数即可。isinstance的第一参数传入带判断的形参;第二个参数就是合法的类型。
def recoder(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类型
函数调用中的参数传递及影响
函数调用时, 实参传递到形参的过程中有两种情况一一传值和传用。
对于不可变对象, 函数调用时, 是传值。意味着函数体里可以使用实参的值, 但不能改变实参。
对于可变对象, 函数调用时, 是传引用。意味着在函数体里还可以对实参进行修改。
匿名函数与可迭代函数
匿名函数一般适用于单行代码函数。
匿名函数的作用是, 把某一简单功能的代码封装起来, 让整体代码更规整一些。一般只调用一次不再需要, 所以名字也省略了, 于是变成了匿名函数。
匿名函数是以关键宇lambda开始的, 它的写法如下:
Lambda参数1, 参数2...:表达式
上面的写法中, 表达式的内容只能是一句话的函数, 而且不能存在return关键字。
可迭代函数就是一种有循环迭代功能的内置函数,包括reduce、map和filter等。
匿名函数与reduce函数的组合应用
reduce函数本质上也可以算作一个内嵌循环的函数。
reduce函数一般用于归并性任务。
reduce函数的能是按照参数sequence中的元素顺序, 来依次调用函数function, 并且每次调用都会向其传入两个参数:一个是sequence序列中的当前元素, 另一个是sequence序列中上一个元素在函数function中的返回值。
reduce( function, sequence, [initial])
function:要回调的函数。
sequence:一个“序列”类型的数据。
第三个参数可选, 是一个初始值。
使用reduce函数与匿名函数的结合写法, 来实现求l~100的和:
from functools import reduce #导入reduce函数
print(reduce(lambda x, y:x+y, range(l,101))) #第一个参数是个匿名函数, 实现两个数相加, 输出:5050
匿名函数与map数的组合应用
map函数的功能是:将参数sequence内部的元素作为参数, 并按照sequence序列的顺序, 依次调用回调函数function。
map函数一般用于映射性任务
map(function, sequence[, sequence, ……])
function:要回调的函数。
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]
2、使用map函数处理多个序列数据
当map后面直接跟多个序列数据时, 处理函数的参数个数要与序列数据的个数相同。
运行时, map内部依次提取每个序列数据中的元素, 一放到所提供的处理函数中, 直到循环遍历完最短的那个序列。例如:
t = map(lambda x,y: x + y, [1,2,3,4,5],[1,2,4,5]) #遍历最小的列表[1,2,4,5],实现两个列表的元素相加
print(list(t)) #将t转成列表类型, 并打印。输出:[2,4,7,9]
匿名函数与filter函数的组合应用
filter函数的功能是对指定序列进行过滤。
fliter函数一般用于过滤性任务。
filter函数的定义
filter函数有两个输入参数:一个是filter的处理函数, 另一个是待处理的序列对象。在运行时, filter函数会把序列对象中的元素依次放到filter的处理函数中。如果返回True, 就留下, 反之就舍去。
其定义如:filter(function or None, sequence)
function:为filter的处理函数, 在filt町的内部以回调的方式被调用。返回布尔型。意味着某元素否要留下。
sequence:一个或多个“序列”类型的数据。
filter函数的使用
filter函数的返回值是一个filter类型, 需要将其转成列表或元组等序列才可以使用。例如:
t=filter(lambda x:X%2==0,[1,2,3,4,5,6,7,8,9,10]) #筛选出一个列表中为偶数的元素
print(list(t)) #转成列表, 并打印。输出[2,4,6,8,10]
如果filter的处理函数为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函数的调用部分代码固定下来。为处理函数变量值不同的过滤函数, 以实现不同的处理功能。
可迭代函数的返回值
每个可选代函数返回的值都属于一个生成器对象。
生成器对象会有一个_next_方法。调用该方法可以取到内部的各个值。最终_next_通过Stoplteration异常来停止迭代。
#过滤列表中能被4整除的元素 返回4、8
t=filter (lambda x:x %4==0 , [l, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(t._next_()) #使用_next_()方法获取元素,输出:4
print(t._next_()) #使用_next_()方法获取元素,输出:8
print(t._next_()) #已经没有元素,返回Stoplteration
生成器对象与迭代器对象的主要区别在于:生成器对象只能选代一次而选代器对象可以法代多次。所以, 对可选代函数的结果只能取一次, 无法再次提取。这一点一定要注意。
偏函数
偏函数是对原始函数的二次封装, 它是属于寄生在原始函数上的函数。偏函数可以理解为重新定义一个函数, 向原始函数添加默认参数。有点像面向对象中的父类与子类的关系。
偏函数的定义:partial(func , *args, **keywords)
func:要封装的原函数;
第二个参数为一个元组或列表的解包参数:代表传入原函数的默认值(可以不指定参数名)
第三个参数为一个字典的解包参数:代表传入原函数的默认值(指定参数名)。
偏函数的作用是, 为其原函数指定一些默认的参数。调用该偏函数, 就相当于调用了原函数, 同时将默认的参数传入。在使用partial前, 必须引入functools模块
偏函数的使用:
from functools import partial
def recoder(strname , age):
print('姓名:', strname, '年纪:', age)
Garyfun = partial(recoder , strname ="Gary") #定义一个偏函数
Garyfun(age = 32) #调用偏函数,输出“姓名:Gary年纪:32“
偏函数的本质是, 将函数式编程、默认参数和冗余参数结合在一起。通过偏函数传入的参数调用关系, 与正常函数的参数调用关系是一致的。
偏函数通过将任意数量(顺序)的参数, 转化为另一个带有剩余参数的函数对象, 从而实现了截取函数功能(偏向〉的效果在实际应用中, 可以使用一个原函数, 然后将其封装多个偏函数, 在调用函数时全部调用偏函数。这样的代码可读性提升了很多。
生成器函数
生成器与迭代器区别:
迭代器是所有的内容都在内存里, 使用next函数来依次往下遍历。
生成器不会把内容放到内存里, 每次调用next函数时, 返回的都是本次计算出来的那个元素, 用完之后立刻销毁。
整个序列占用内存特别大时, 使用生成器对象会节约内存;当系统的运算资源不足时, 使用迭代器对象会节约运算资源。
生器函数(Generator)是一种特殊的函数, 是用来创建生成器对象的工具。生成器函数使用yield语句返回, 返回值是一个生成器对象。
def Reverse(data} : #定义一个函数实现字符串反转
for idx in range(len(data)-1 , - 1, -1) :
yield data [ idx ] #使用yield返回具体的一个元素
for c in Reverse('Python'):
print(c, end = ' ') #输出 n o h t y P