5.1 函数的定义和调用
在python中,函数是具有名称,参数、返回值的代码块。
python支持自定义函数,函数是组织好的、可重复利用的、用来实现单一或相关联功能的代码段,能够提高应用的模块化和代码的重复利用率。
5.1.1 定义函数
函数有三个重要的元素:函数名、函数参数、返回值。其中,函数名是必须的,函数参数和返回值是可选的。
定义函数的基本语法格式:
def 函数名(参数列表):
"""函数注释字符串"""
函数体
例如:定义一个查看函数说明文档的函数。
help 函数的作用是查看函数的说明文档。
5.1.2 调用函数
基本语法格式:
函数名称([函数参数])
例如:调用函数
5.1.3 函数的返回值
为函数设置返回值的作用就是将函数的处理结果返回给调用它的函数。所谓“返回值”,就是程序中的函数完成一件事之后,最后返给调用者的结果。
return [value]
value:指定需要返回的值。
例如:定义两个数相加的函数。
return 语句的作用:
1.退出当前函数。return 下方的所有代码(函数体内)都不执行。
2.负责函数返回值。
如果一个函数要有多个返回值,可以采取“ return a,b ”的写法,返回多个数据的时候,默认是元组类型。return后面也可以跟着列表、元组或字典,以返回多个值。
5.1.4 函数的嵌套调用
在一个函数中调用了另外一个函数,即函数嵌套调用。其执行流程为:如果函数A中调用了另外一个函数B,那么先执行完B中的所有任务,再回到上次A中执行的位置。
例如:函数嵌套调用实例。
5.2 函数的参数与值传递
函数参数的作用:
1.传递数据给函数使用,函数利用接收的数据进行具体的操作处理。
2.函数调用的时候可以传入真实的数据,以增加函数使用的灵活性。
5.2.1 函数的形参和实参
形式参数:在定义函数时,函数名之后括号内的参数就是形式参数,形参对于调用者来说是透明的,即形参叫什么与调用者无关。形参是在函数内部使用的,函数外部不可见。
实际参数:在调用一个函数的时候,函数名之后括号内中的参数就是实际参数。
根据实际参数的类型不同,可分为将实际参数的值传递给形式参数和将实际参数的引用传递给形式参数两种情况。其中,当实际参数为不可变对象时,为将实际参数的值传递给形式参数,进行的是值传递。当实际参数的值为可变对象时,为将实际参数的引用传递给形式参数,进行的是引用传递。
值传递与引用传递的基本区别:进行值传递时,改变形式参数的值。进行引用传递时,既改变形式参数的值也改变实际参数的值。
例如:定义一个printString函数,分别使用值传递和引用传递,观察形参和实参的变化。
可以看到的是,值传递时,实际参数 str1 没有发生改变,而引用传递时,实参L1发生了改变。
5.2.2 位置参数(必备参数)
调用函数时,根据定义函数的位置参数来传递参数。当调用函数时,传入的位置参数是和定义函数的位置参数对应的,即调用时的位置和数量必须和定义是一致的。
当调用时传入的参数的数量与定义不一致时会直接报错。
当调用时传入的参数的位置与定义不一致时可能出现以下两种结果:
1.形参与实参的类型一致,但是产生的结果和预期不一致。
2.形参与实参的类型不一致不能正常转换报错。
例如:位置参数的应用实例。
当传入时的参数位置与name、prize不一致时就会发生不会报错的但是输出内容并不符合要求的错误。
5.2.3 关键字参数
即使用形式参数的名字来确定输入的参数值,在函数调用时采用“键 = 值”的方法,这样就可以忽略掉在传入参数时的位置,使得函数的调用和参数的传递变得更加灵活。如以上例子在输出时用"print(winprize(name = ‘黄惠成’, prize = ‘一’))" 这样的语句就能忽略掉形参的位置。
例如:关键字参数应用实例。
5.2.4 默认参数
默认参数:就是给参数设置一个默认值,这个参数就叫做默认参数。
在调用函数时,如果没有指定某个参数,系统将抛出异常,为了解决这个问题,就可以给函数的参数设置一个默认值,这样,当没有传入参数时,则直接使用定义函数时设置的默认值。
默认参数的位置必须位于所有参数的最后,否则会产生语法错误。
在调用函数时,传入参数的时候可以直接忽略掉默认参数,如果给默认参数传入了一个新的值,则使用传入的新值代替。
例如:
注意:默认参数必须指向不可变对象,如数值。
5.2.5 不定长(可变)参数
用于不确定调用的时候会传入多少个参数的场景。
可变参数即传入函数中的参数可以是0个也可以是任意个。
在定义一个函数时,若希望函数能够处理的参数个数比当初定义的多则可以使用不定长参数。
定义可变参数时,有 *args 和 **kwargs 两种形式。
1.*args 形式
这种形式表示接收任意多个实际参数并将其放到一个元组中去。
例如:定义一个函数,让它可以接受任意多个实际参数。
若想要传入的参数是一个已经存在的列表则可以在列表的名字前加上“ * ”:
list1 = ['北京大学', '清华大学', '中国农业大学']
printshool(*list1)
2.**kwargs
当 **kwargs 作函数形参时,表示接受0个或任意多个默认参数。
当 kwargs 作函数实参时,表示将字典转换为默认参数。
kwargs实际上是一个字典。
例如:定义一个函数,让其可以接受任意多个显式赋值的实际参数。
如果想要使用一个已经存在的字典作为参数传入则可以使用字典名:
name = {安徽 = '皖',河北 = '冀', 北京 = '京' }
printProvince(**name)
可以看出来的是,再将列表和字典作为参数传入函数中的区别是,列表只用一个“ * ” ,字典用两个“ * ”
5.2.6 可变参数的装包与拆包
装包:将命名参数和未命名参数分别放在重点或者元组中。
拆包:将一个序列类型的数据拆分为多个数据,分别赋值给变量,并且位置对应。
args是元组类型,kwargs是字典类型。
例如:参数args 的装包与拆包过程示例。
从上述例子可以看出:
1.传进去的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组,args是元组类型。
2.形参中的args其实真正接收的数据是args,它是一个元组,把传进来的数据放在了args这个元组中。
3.函数体里的args依然是那个元组,但是args表示的就是对args进行拆包,将元组中的数据拆成单个数据。
4.对于args这个元组,如果不对其进行拆包就将其传入以args作为形参的函数时,args会被看成一个整体,作为一个类型为元组的数据被传入。
例如:参数**kwargs的装包与拆包过程示例。
此处并不能将拆包后的kwargs打印出来,会直接报错。
5.3 变量的作用域
变量的作用域就是指程序代码能够访问该变量的区域,如果超出了该区域,在访问时就会出现错误,即变量生效的范围。
在程序中,根据变量的有效范围可以将变量分为“全局变量”和“局部变量”
5.3.1 LEGB 原则
在python检索变量的时候,先是在局部中查找,如果找不到,就会去局部外的局部找,再找不到就去全局找,再者去内建命名空间找。LEGB对应的就是这样一个检索范围从小到大的原则。
5.3.2 全局变量和局部变量
1.局部变量
局部变量指在函数内部定义使用的变量,它只在函数内部有效。因此,如果在函数外部使用了函数内部定义的变量就会报错。
通过def定义的的函数内的变量名,只能在def函数内使用,它与函数外部具有相同名称的变量没有任何关系。不同名称的函数可以定义相同名称的局部变量,并且各个函数内的变量不会产生影响。
局部变量的作用是,在函数体内临时保存数据,当函数调用完成后则销毁局部变量。
例如:局部变量的使用示例。
2.全局变量
全局变量能够在整个程序范围内被访问,全局变量为能够作用与函数内外的变量。
全局变量主要有以下两种状况:
(1)在函数外定义变量
如果一个变量在函数外被定义,那么不仅可以在函数外访问它也可以在函数内访问它。
例如:全局变量和局部变量的应用示例。
(2)在函数体内定义变量
如果一个变量在函数体内定义但是经过 global 关键字修饰,该变量也就成了全局变量。在函数体外也可以访问该变量,并且在函数体内还可以对其进行修改。
例如:global 关键字示例。
5.4 递归函数和匿名函数
5.4.1递归函数
函数内部调用自身,即递归函数。
例如:利用递归函数计算5!
递归函数的特征:
1.递归函数必须具有一个明确的结束条件。
2.递归的调用和返回过程,跟入栈和出栈类似。(递归调用的次数越多会导致栈内存的溢出)
5.4.2 匿名函数
匿名函数是指没有名字的函数,不使用 def 语句定义,应用在需要使用一个函数但是不想去思考函数名称的场合。通常情况下,匿名函数只使用一次。
在python中,使用lambda表达式创建匿名函数。
result = lambda[arg1[,arg2,...,argn]]:expression
在匿名函数中,参数可以有多个,但是表达式只能有一个,即只能返回一个值,还不能有非表达式的语句。(如for、whlie语句)
匿名函数不能直接调用print
假设要对两个数进行运算,如果希望声明的函数支持所有的运算,则可以将匿名函数作为函数参数传递。
例如:匿名函数的高级应用示例。
例如:匿名函数用作函数参数传递的示例。
匿名函数与def函数的区别:
1.def语句创建的函数是有名字的,匿名函数是没有名字的。
2.lambda 表达式返回的结果往往是一个表达式或者对象,它不会将结果赋给一个变量,但是def函数可以实现。
3.lambda 表达式只能有一个表达式,而def函数可以有多个表达式。
4.if、for、while等语句不能用在 lambda 表达式里但是 def 可以。
5.lambda 一般定义的是简单的函数,def 定义的可以是复杂的函数。
6.lambda 表达式不能分享给其他程序调用,但 def 可以。
7.lambda 的函数体比较简单,def 函数体更加复杂。
5.5 高阶函数
高阶函数:一个可以接受另一个函数作为参数的函数。
高阶函数的一大特征就是有一个即以上的函数作为自己的参数。
例如:定义高阶函数示例。
abs函数表示进行绝对值的计算。
5.5.1 内置高阶函数:map
python中的map函数会根据提供的函数对指定的序列做映射。
生成的结果会组成一个新的迭代器并返回。
map(func, *iterables)
func:函数名。
iterables:一个可以迭代的对象。(列表、元组、字符串)
例如:运用map函数,对列表中的每一个元素进行平方并将其输出。
可以看到的是,如果只是单纯输出经过 map 函数转化后的返回值其实是一个迭代器,想要将这个迭代器转化成列表则要用到 list 函数。
以上还可以用匿名函数作为参数传入 map 函数中去。
5.5.2 内置高阶函数:reduce
reduce(func,iterable[,initializer])
func :函数,必须有两个参数。
iterable:可迭代对象。
initializer :可选项,初始参数。
reduce(a,b)的工作过程:假设 a 是一个二元函数, b 是一个列表,那么 reduce 的功能就是将 b 中的一二个元素传入到 a 中进行运算,再将得到的结果和 b 中第三个元素传入 a 中进行运算,以此类推。
例如:reduce函数应用实例。
可以发现的是,最后 result 并不是一个迭代器,这是与上面 map 函数的区别。
这个例子同样可以使用匿名函数。只需将代码修改成如下:
result = reduce(lambda x,y: x+y, [1,2,3,4,5,6,7])
5.5.3 内置高阶函数:filter
filter 函数会对指定序列进行过滤操作。filter 函数接受一个 f 和一个 list ,这个函数 f 的作用是对每个元素进行判断,返回 True 或者False,filter 根据判断结果自动过滤掉不符合条件的元素,返回由符合条件的元素组成的一个新的 list ,但是 filter 和 map 一样,返回的值需要通过一个 list 函数转化成列表。
filter(func, iterable)
func : 判断函数,它只能接收一个参数,并且返回值是布尔值。
例如:利用 filter 删除列表中的偶数。
同理,匿名函数在这个例子中同样适用。
再例如:利用 fliter 函数删除列表中的空字符串和None。
5.6 闭包及其应用
5.6.1 闭包概述
闭包须满足的三个条件:
1.存在函数嵌套。
2.内部函数使用了外部函数的变量。(或者参数)
3.外部函数返回了内部函数。
闭包的作用:
- 闭包可以保存外部函数的变量,不会随着外部函数调用完而销毁。
- 也由于闭包使用了外部函数的变量,所以外部函数的变量没有及时释放,会消耗内存。
例如:闭包的定义示例
需要注意的,上述例子在创建好闭包时,将其赋予给了另外的一个新变量,这样做的目的就是为了能够调用并且执行这个闭包,通过‘‘ 变量名()’’来实现.
5.6.2 闭包的应用
闭包主要在面向对象、装饰器和实现单例模式三个方面的应用比较多。
例如:使用闭包实现 y = ax+b 的操作
闭包可以提高代码的可复用性。
5.7 装饰器及其应用
装饰器的作用:为一个目标函数添加额外的功能但又不修改函数本身。
装饰器本身其实是一个函数。
5.7.1 装饰器的概念
即用于拓展原来函数功能的一种函数。
例如:使用装饰器·@timeit来测试函数运行时间。
其中的@timeit是一个应用了闭包的函数。
5.7.2 装饰器的应用
1.无参数的装饰器的应用
例如:通过测试函数运行时间演示无参数的装饰器应用。
2.有参数的装饰器应用
例如:通过测试函数运行时间演示有参数的装饰器的应用。
3.装饰器既可以装饰带参函数也可以装饰不带参函数
例如:通过测试运行函数时间演示有或无参数的装饰器应用。
5.8 迭代器及其应用
5.8.1 迭代器的概念
1.迭代
迭代是通过重复执行的代码处理类似的数据集的过程,并且本次处理的结果要依赖于上一次的处理结果,上一次的结果是下一次的初始状态,如果途中有任何停顿,都不能算迭代。
例如:非迭代应用示例以及迭代应用示例。
第一个例子之所以不是迭代就是因为其本次的输出的结果与上一次的循环值‘‘i’’无关。
第二个例子,本次的输出结果与上一次的循环值‘‘k’’有关,所以是迭代。
2.容器
容器是把多个数据结构集合在一起的数据结构。
容器仅仅是用来存放数据的,它并没有取出元素的能力,是可迭代对象赋予了容器这种能力。
3.可迭代对象
可迭代对象是指储存了元素的容器对象,且容器中的元素可以通过–iter–()方法和–getitem–()方法访问。
(1)–iter–()方法是让对象可以通过for…in循环遍历,–getitem–()是让对象可以通过‘实例名【index】’去访问实例中的元素。
(2)一个可迭代对象是不能独立进行迭代的,需要通过for…in循环来实现迭代。
(3)常见的可迭代对象:list,tuple、dict、set、str。
(4)通过collection模块的Iterable类型判断对象是否为可迭代对象。
from collection import Iterable
isinstance('',Iterable) # 返回Ture,表明字符串也是可迭代对象。
4.迭代器
迭代器可以看做是一个特殊的对象,每次调用该对象时会返回自身的下一个元素,从实现上来看,一个迭代器对象必须是定义了–iter–()和next()的对象.
迭代器对象可以被next函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
所有的可迭代对象都可以通过iter()方法来转变成迭代器。
例如:列表生成迭代器的示例。
5.8.2 迭代器的应用
1.用for…in方式访问迭代器
可以看出来的是用for循环访问迭代器和列表的区别就是迭代器只能取一次元素,而列表可以无限次取元素。
2.用next方式访问
next()可以从迭代器中访问下一个数据,当下一个数据为空时触发stoptreration异常。
5.9 生成器及其应用
5.9.1 生成器的概念
生成器是能够动态提供数据的可迭代对象。这里的动态是指循环一次、计算一次、返回一次。
生成器函数
定义:含有yield语句的函数,返回值为生成器对象。
语法:
def 函数名():
...
yield 数据
...
调用:
for 变量名 in 函数名():
语句
调用生成器函数将返回一个生成器对象,不执行函数体。
执行过程:
(1)调用生成器函数会自动创建迭代器对象。
(2)在调用迭代器对象的next()方法才可以执行生成器函数。
(3)每次执行到yield语句时返回数据,暂时离开。
(4)待下次调用next()方法时继续从离开处继续执行。
生成迭代器对象规则如下;
1.将yield关键字之前的代码放在next方法中。
2.将yield关键字之后的数据作为next方法的返回值。
5.9.2 生成器的应用
生成器的主要目的是构成一个用户自定义的循环对象。它可以看作是一个带有yield的函数,其中yield是一个关键字,一旦函数被yield修饰,python解释器会将被修饰的函数看作一个生成器。
生成器应用示例:
代码第一次执行输出:‘生成器被执行’、1
原路返回,代码第二次执行输出:2
代码第三次执行,利用for循环对生成器对象进行遍历。
再例如:利用生成器实现前项加后项等于下一项的操作。