《Python从小白到大牛》已经上市!
程序中反复执行的代码能够封装到一个代码块中,这个代码块模仿了数学中的函数,具备函数名、参数和返回值,这就是程序中的函数。html
Python中的函数很灵活,它能够在模块中,但类以外定义,即函数,做用域是当前模块;也能够在别的函数中定义,即嵌套函数;还能够在类中定义,即方法。python
在前面的学习过程当中也用到了一些函数,若是len()、min()和max(),这些函数都由Python官方提供的,称为内置函数(Built-in
Functions, 缩写BIF)。程序员
注意
Python做为解释性语言函数必须先定义后调用,也就是定义函数必须在调用函数以前,不然会有错误发生。算法
本节介绍自定义函数,自定义函数的语法格式以下:shell
def 函数名(参数列表) : 函数体 return 返回值
在Python中定义函数时,关键字是def,函数名须要符合标识符命名规范;多个参数列表之间能够用逗号(,)分隔,固然函数也能够没有参数。若是函数有返回数据,就须要在函数体最后使用return语句将数据返回;若是没有返回数据,则函数体中可使用return
None或省略return语句。编程
函数定义示例代码以下:安全
# coding=utf-8 # 代码文件:chapter10/ch10.1.py def rectangle_area(width, height): ① area = width * height return area ② r_area = rectangle_area(320.0, 480.0) ③ print("320x480的长方形的面积:{0:.2f}".format(r_area))
上述代码第①行是定义计算长方形面积的函数rectangle_area,它有两个参数,分别是长方形的宽和高,width和height是参数名。代码第②行代码是经过return返回函数计算结果。代码第③行是调用rectangle_area函数。数据结构
Python中的函数参数很灵活,具体体如今传递参数有多种形式上。本节介绍几种不一样形式参数和调用方式。app
为了提升函数调用的可读性,在函数调用时可使用关键字参数调用。采用关键字参数调用函数,在函数定义时不须要作额外的工做。编程语言
示例代码以下:
# coding=utf-8 # 代码文件:chapter10/ch10.2.1.py def print_area(width, height): area = width * height print("{0} x {1} 长方形的面积:{2}".format(width, height, area)) print_area(320.0, 480.0) # 没有采用关键字参数函数调用 ① print_area(width=320.0, height=480.0) # 采用关键字参数函数调用② print_area(320.0, height=480.0) # 采用关键字参数函数调用 ③ # print_area(width=320.0, height) # 发生错误 ④ print_area(height=480.0, width=320.0) # 采用关键字参数函数调用 ⑤
print_area函数有两个参数,在调用时没有采用关键字参数函数调用,见代码第①行,也可使用关键字参数调用函数,见代码第②行、第③行和第⑤行,其中width和height是参数名。从上述代码比较可见采用关键字参数调用函数,调用者可以清晰地看出传递参数的含义,关键字参数对于有多参数函数调用很是有用。另外,采用关键字参数函数调用时,参数顺序能够与函数定义时参数顺序不一样。
注意
在调用函数时,一旦其中一个参数采用了关键字参数形式传递,那么其后的全部参数都必须采用关键字参数形式传递。代码第④行的函数调用中第一个参数width采用了关键字参数形式,而它后面的参数没有采用关键字参数形式,所以会有错误发生。
在定义函数的时候能够为参数设置一个默认值,当调用函数的时候能够忽略该参数。来看下面的一个示例:
# coding=utf-8 # 代码文件:chapter9/ch10.2.2.py def make_coffee(name="卡布奇诺"): return "制做一杯{0}咖啡。".format(name)
上述代码定义了makeCoffee函数,能够帮助我作一杯香浓的咖啡。因为我喜欢喝卡布奇诺,就把它设置为默认值。在参数列表中,默认值能够跟在参数类型的后面,经过等号提供给参数。
在调用的时候,若是调用者没有传递参数,则使用默认值。调用代码以下:
coffee1 = make_coffee("拿铁") ① coffee2 = make_coffee() ② print(coffee1) # 制做一杯拿铁咖啡。 print(coffee2) # 制做一杯卡布奇诺咖啡。
其中第①行代码是传递"拿铁"参数,没有使用默认值。第②行代码没有传递参数,所以使用默认值。
提示
在其余语言中make_coffee函数能够采用重载实现多个版本。Python不支持函数重载,而是使用参数默认值的方式提供相似函数重载的功能。由于参数默认值只须要定义一个函数就能够了,而重载则须要定义多个函数,这会增长代码量。
Python中函数的参数个数能够变化,它能够接受不肯定数量的参数,这种参数称为可变参数。Python中可变参数有两种,在参数前加*或**形式,*可变参数在函数中被组装成为一个元组,**可变参数在函数中被组装成为一个字典。
下面看一个示例:
def sum(*numbers, multiple=1): total = 0.0 for number in numbers: total += number return total * multiple
上述代码定义了一个sum()函数,用来计算传递给它的全部参数之和。*numbers是可变参数。在函数体中参数numbers被组装成为一个元组,可使用for循环遍历numbers元组,计算他们的总和,而后返回给调用者。
下面是三次调用sum()函数的代码:
print(sum(100.0, 20.0, 30.0)) # 输出150.0 print(sum(30.0, 80.0)) # 输出110.0 print(sum(30.0, 80.0, multiple=2)) # 输出220.0 ① double_tuple = (50.0, 60.0, 0.0) # 元组或列表 ② print(sum(30.0, 80.0, *double_tuple)) # 输出220.0 ③
能够看到,每次所传递参数的个数是不一样的,前两次调用时都省略了multiple参数,第三次调用时传递了multiple参数,此时multiple应该使用关键字参数传递,不然有错误发生。
若是已经有一个元组变量(见代码第②行),可否传递给可变参数呢?这须要使用对元组进行拆包,见代码第③行在元组double_tuple前面加上单星号(*),单星号在这里表示将double_tuple拆包为50.0,
60.0, 0.0形式。另外,double_tuple也能够是列表对象。
注意
*可变参数不是最后一个参数时,后面的参数须要采用关键字参数形式传递。代码第①行30.0,
80.0是可变参数,后面multiple参数须要关键字参数形式传递。
下面看一个示例:
def show_info(sep=':', **info): print('-----info------') for key, value in info.items(): print('{0} {2} {1}'.format(key, value, sep))
上述代码定义了一个show_info()函数,用来输出一些信息,其中参数sep信息分隔符号,默认值是冒号(:)。**info是可变参数,在函数体中参数info被组装成为一个字典。
注意 **可变参数必须在正规参数以后,若是本例函数定义改成show_info(**info,sep=':')形式,会发生错误。
下面是三次调用show_info()函数的代码:
show_info('->', name='Tony', age=18, sex=True) ① show_info(sutdent_name='Tony', sutdent_no='1000', sep='-') ② stu_dict = {'name': 'Tony', 'age': 18} # 建立字典对象 show_info(**stu_dict, sex=True, sep='=') # 传递字典stu_dict ③
上述代码第①行是调用函数show_info(),第一个参数'-\>'是传递给sep,其后的参数name='Tony',
age=18,
sex=True是传递给info,这种参数形式事实上就是关键字参数,注意键不要用引号包裹起来。
代码第②行是调用函数show_info(),sep也采用关键字参数传递,这种方式sep参数能够放置在参数列表的任何位置,其中的关键字参数被收集到info字典中。
代码第③行是调用函数show_info(),其中字典对象stu_dict,传递时stu_dict前面加上双星号(**),双星号在这里表示将stu_dict拆包为key=value对的形式。
Python函数的返回值也是比较灵活的,主要有三种形式:无返回值、单一返回值和多返回值。前面使用的函数基本都单一返回值,本节重点介绍无返回值和多返回值两种形式。
有的函数只是为了处理某个过程,此时能够将函数设计为无返回值的。所谓无返回值,事实上是返回None,None表示没有实际意义的数据。
无返回值函数示例代码以下:
# coding=utf-8 # 代码文件:chapter9/ch10.3.1.py def show_info(sep=':', **info): ① """定义**可变参数函数""" print('-----info------') for key, value in info.items(): print('{0} {2} {1}'.format(key, value, sep)) return # return None 或省略 ② result = show_info('->', name='Tony', age=18, sex=True) print(result) # 输出 None def sum(*numbers, multiple=1): ③ """定义*可变参数函数""" if len(numbers) == 0: return # return None 或省略 ④ total = 0.0 for number in numbers: total += number return total * multiple print(sum(30.0, 80.0)) # 输出110.0 print(sum(multiple=2)) # 输出 None ⑥
上述代码定义了两个函数,这个两个函数事实上是在10.3.2节示例基础上的重构。其中代码第①行的show_info()只是输出一些信息,不须要返回数据,所以能够省略return语句。若是必定要使用return语句,见代码第②行在函数结束前使用return或return
None。
对于本例中的show_info()函数强加return语句显然是画蛇添足,可是有时使用return或return
None是必要的。代码第③行定义了sum()函数,若是numbers中数据是空的,后面的求和计算也就没有意义了,能够在函数的开始判断numbers中算法有数据,若是没有数据则使用return或return
None跳出函数,见代码第④行。
有时须要函数返回多个值,实现返回多个值的实现方式有不少,简单实现是使用元组返回多个值,由于元组做为数据结构能够容纳多个数据,另外元组是不可变的,使用起来比较安全。
下面来看一个示例:
# coding=utf-8 # 代码文件:chapter9/ch10.3.2.py def position(dt, speed): ① posx = speed[0] * dt ② posy = speed[1] * dt ③ return (posx, posy) ④ move = position(60.0, (10, -5)) ⑤ print("物体位移:({0}, {1})".format(move[0], move[1])) ⑥
这个示例是计算物体在指定时间和速度时的位移。第①行代码是定义position函数,其中dt参数是时间,speed参数是元组类型,speed第一个元素X轴上的速度,speed第二个元素Y轴上的速度。position函数的返回值也是元组类型。
函数体中的第②行代码是计算X方向的位移,第③行代码是计算Y方向的位移。最后,第④行代码将计算后的数据返回,(posx,
posy)是元组类型实例。
第⑤行代码调用函数,传递的时间是60.0秒,速度是(10,
-5)。第⑥行代码打印输出结果,结果以下:
物体位移:(600.0, -300.0)
变量能够在模块中建立,做用域是整个模块,称为全局变量。变量也能够在函数中建立,默认状况下做用域是整个函数,称为局部变量。
示例代码以下:
# coding=utf-8 # 代码文件:chapter9/ch10.4.py # 建立全局变量x x = 20 ① def print_value(): print("函数中x = {0}".format(x)) ② print_value() print("全局变量x = {0}".format(x))
输出结果:
函数中x = 20 全局变量x = 20
上述代码第①行是建立全局变量x,全局变量做用域是整个模块,因此在print_value()函数中也能够访问变量x,见代码第②行。
修改上述示例代码以下:
# 建立全局变量x x = 20 def print_value(): # 建立局部变量x x = 10 ① print("函数中x = {0}".format(x)) print_value() print("全局变量x = {0}".format(x))
输出结果:
函数中x = 10 全局变量x = 20
上述代码的是print_value()函数中添加x =
10语句,见代码第①行,这会函数中建立x变量,函数中的x变量与全局变量x命名相同,在函数做用域内会屏蔽全局x变量。
函数中建立的变量默认做用域是当前函数,这能够防止程序员少犯错误,由于函数中建立的变量,若是做用域是整个模块,那么在其余函数中也能够访问,Python没法从语言层面定义常量,因此在其余函数中因为误操做修改了变量,这样一来很容易致使程序出现错误。即使笔者不赞同,但Python提供这种可能,经过在函数中将变量声明为global的,能够把变量的做用域变成全局的。
修改上述示例代码以下:
# 建立全局变量x x = 20 def print_value(): global x ① x = 10 ② print("函数中x = {0}".format(x)) print_value() print("全局变量x = {0}".format(x))
输出结果:
函数中x = 10 全局变量x = 10
代码第①行是在函数中声明x变量做用域为全局变量,因此代码第②行修改x值,就是修改全局变量x的数值。
在一个函数中使用return关键字返回数据,可是有时候会使用yield 关键字返回数据。yield 关键字的函数返回的是一个生成器(generator)对象,生成器对象是一种可迭代对象。
若是想计算平方数列,一般的实现代码以下:
def square(num): ① n_list = [] for i in range(1, num + 1): n_list.append(i * i) ② return n_list ③ for i in square(5): ④ print(i, end=' ')
返回结果是:
1 4 9 16 25
首先定义一个函数,见代码第①行。代码第②行经过循环计算一个数的平方,并将结果保存到一个列表对象n_list中。最后返回列表对象,见代码第③行。代码第④行是遍历返回的列表对象。
在Python中还能够有更加好解决方案,实现代码以下:
def square(num): for i in range(1, num + 1): yield i * i ① for i in square(5): ② print(i, end=' ')
返回结果是:
1 4 9 16 25
代码第①行使用了yield关键字返回平方数,再也不须要return关键字了。代码第②行调用函数square()返回的是生成器对象。生成器对象是一种可迭代对象,可迭代对象经过next()方法得到元素,代码第②行的for循环可以遍历可迭代对象,就是隐式地调用生成器的next()方法得到元素的。
显式地调用生成器的next()方法,在Python Shell中运行示例代码以下:
>>> def square(num): for i in range(1, num + 1): yield i * i >>> n_seq = square(5) <generator object square at 0x000001C8F123CE60> >>> n_seq.__next__() ① 1 >>> n_seq.__next__() 4 >>> n_seq.__next__() 9 >>> n_seq.__next__() 16 >>> n_seq.__next__() 25 >>> n_seq.__next__() ② Traceback (most recent call last): File "<pyshell#33>", line 1, in <module> n_seq.__next__() StopIteration >>>
上述代码第①行\~第②行调用了6次next()方法,但第6次调用则会抛出StopIteration异常,这是由于已经没有元素可迭代了。
生成器函数是经过yield返回数据,与return不一样的是:return语句一次返回全部数据,函数调用结束;而yield语句只返回一个元素数据,函数调用不会结束,只是暂停,直到next()方法被调用,程序继续执行yield语句以后的语句代码。这个过程如图10-1所示。
注意
生成器特别适合用于遍历一些大序列对象,它无须将对象的全部元素都载入内存后才开始进行操做。而是仅在迭代至某个元素时才会将该元素载入内存。
在本节以前定义的函数都是全局函数,并将他们定义在全局做用域中。函数还可定义在另外的函数体中,称做“嵌套函数”。
示例代码:
# coding=utf-8 # 代码文件:chapter10/ch10.6.py def calculate(n1, n2, opr): multiple = 2 # 定义相加函数 def add(a, b): ① return (a + b) * multiple # 定义相减函数 def sub(a, b): ② return (a - b) * multiple if opr == '+': return add(n1, n2) else: return sub(n1, n2) print(calculate(10, 5, '+')) # 输出结果是30 # add(10, 5) 发生错误 ③ # sub(10, 5) 发生错误 ④
上述代码中定义了两个嵌套函数:add()和sub(),见代码第①行和第②行。嵌套函数能够访问所在外部函数calculate()中的变量multiple,而外部函数不能访问嵌套函数局部变量。另外,嵌套函数的做用域在外部函数体内,所以在外部函数体以外直接访问嵌套函数会发生错误,见代码第③行和第④行。
函数式编程(functional
programming)与面向对象编程同样都一种编程范式,函数式编程,也称为面向函数的编程。
Python并非完全的函数式编程语言,可是仍是提供了一些函数式编程必备的技术,主要有:函数类型和Lambda表达式,他们是实现函数式编程的基础。
Python提供了一种函数类型function,任何一个函数都有函数类型,可是函数调用时,就建立了函数类型实例,即函数对象。函数类型实例与其余类型实例同样,在使用场景上没有区别:它能够赋值给一个变量;也能够做为参数传递给一个函数;还能够做为函数返回值使用。
为了理解函数类型,先重构10.6节中嵌套函数的示例,示例代码以下:
# coding=utf-8 # 代码文件:chapter10/ch10.7.1.py def calculate_fun(opr): ① # 定义相加函数 def add(a, b): return a + b # 定义相减函数 def sub(a, b): return a - b if opr == '+': return add ② else: return sub ③ f1 = calculate_fun('+') ④ f2 = calculate_fun('-') ⑤ print(type(f1)) print("10 + 5 = {0}".format(f1(10, 5))) ⑥ print("10 - 5 = {0}".format(f2(10, 5))) ⑦
输出结果:
<class 'function'> 10 + 5 = 30 10 - 5 = 10
'+'为True时返回add函数名,不然返回sub函数名,见代码第③行。这里的函数名本质上函数对象。calculate_fun()函数与10.5节示例calculate()函数不一样之处在于,calculate_fun()函数返回的是函数对象,calculate()函数返回的是整数(相加或相减计算以后的结果)。因此代码第④行的f1是add()函数对象,代码第⑤行的f2是sub()函数对象。
函数对象是能够与函数同样进行调用的。事实上在第⑥行以前没有真正调用add()函数进行相加计算,f1(10,
5)表达式才真的调用了add()函数。第⑦行的f2(10, 5)表达式是调用sub()函数。
理解了函数类型和函数对象,学习Lambda表达式就简单了。Lambda表达式本质上一种匿名函数,匿名函数也是函数有函数类型,也能够建立函数对象。
定义Lambda表达式语法以下:
lambda 参数列表 : Lambda体
lambda是关键字声明这是一个Lambda表达式,“参数列表”与函数的参数列表是同样的,但不须要小括号包裹起来,冒号后面是“Lambda体”,Lambda表达式主要的代码在此处编写,相似于函数体。
注意
Lambda体部分不能是一个代码块,不能包含多条语句,只有一条语句,语句会计算一个结果返回给Lambda表达式,可是与函数不一样是,不须要使用return语句返回。与其余语言中的Lambda表达式相比,Python中提供Lambda表达式只能处理一些简单的计算。
重构10.7.1节示例,代码以下:
# coding=utf-8 # 代码文件:chapter10/ch10.7.2.py def calculate_fun(opr): if opr == '+': return lambda a, b: (a + b) ① else: return lambda a, b: (a - b) ② f1 = calculate_fun('+') f2 = calculate_fun('-') print(type(f1)) print("10 + 5 = {0}".format(f1(10, 5))) print("10 - 5 = {0}".format(f2(10, 5)))
输出结果:
<class 'function'> 10 + 5 = 30 10 - 5 = 10
上述代码第①行替代了add()函数,第②行替代了sub()函数,代码变的很是的简单。
函数式编程本质是经过函数处理数据,过滤、映射和聚合是处理数据的三大基本操做。针对但其中三大基本操做Python提供了三个基础的函数:filter()、map()和reduce()。
过滤操做使用filter()函数,它能够对可迭代对象的元素进行过滤,filter()函数语法以下:
filter(function, iterable)
其中参数function是一个函数,参数iterable是可迭代对象。filter()函数调用时iterable会被遍历,它的元素逐一传入function函数,function函数返回布尔值,在function函数中编写过滤条件,若是为True的元素被保留,若是为False的元素被过滤掉。
下面经过一个示例介绍一下filter()函数使用,示例代码以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_filter = filter(lambda u: u.startswith('T'), users) ① print(list(users_filter))
输出结果:
['Tony', 'Tom']
代码第①行调用filter()函数过滤users列表,过滤条件是T开通的元素,lambda u:
u.startswith('T')是一个Lambda表达式,它提供了过滤条件。filter()函数还不是一个列表,须要使用list()函数转换过滤以后的数据为列表。
再看一个示例:
number_list = range(1, 11) nmber_filter = filter(lambda it: it % 2 == 0, number_list) print(list(nmber_filter))
该示例实现了获取1\~10数字中的偶数,输出结果以下:
[2, 4, 6, 8, 10]
映射操做使用map()函数,它能够对可迭代对象的元素进行变换,map()函数语法以下:
map(function, iterable)
其中参数function是一个函数,参数iterable是可迭代对象。map()函数调用时iterable会被遍历,它的元素逐一传入function函数,在function函数中对元素进行变换。
下面经过一个示例介绍一下map()函数使用,示例代码以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_map = map(lambda u: u.lower(), users) ① print(list(users_map))
输出结果:
['tony', 'tom', 'ben', 'alex']
上述代码第①行调用map()函数将users列表元素转换为小写字母,变换使用Lambda表达式lambda
u:
u.lower()。map()函数返回的还不是一个列表,须要使用list()函数将过滤以后的数据转换为列表。
函数式编程时数据能够从一个函数“流”入另一个函数,可是遗憾的是Python并不支持“链式”API。例如想获取users列表中“T”开通的名字,再将其转换为小写字母,这样的需求须要使用filter()函数进行过滤,再使用map()函数进行映射变换。实现代码以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_filter = filter(lambda u: u.startswith('T'), users) # users_map = map(lambda u: u.lower(), users_filter) ① users_map = map(lambda u: u.lower(), filter(lambda u: u.startswith('T'), users)) ② print(list(users_map))
上述代码第①行和第②行实现相同功能。
聚合操做会将多个数据聚合起来输出单个数据,聚合操做中最基础的是概括函数reduce(),reduce()函数会将多个数据按照指定的算法积累叠加起来,最后输出一个数据。
reduce()函数语法以下:
reduce(function, iterable[, initializer])
参数function是聚合操做函数,该函数有两个参数,参数iterable是可迭代对象;参数initializer初始值。
下面经过一个示例介绍一下reduce()函数使用。下面示例实现了对一个数列求和运算,代码以下:
from functools import reduce ① a = (1, 2, 3, 4) a_reduce = reduce(lambda acc, i: acc + i, a) # 10 ② # a_reduce = reduce(lambda acc, i: acc + i, a, 2) # 12 ③ print(a_reduce)
reduce()函数是在functools模块中定义的,因此要使用reduce()函数须要导入functools模块,见代码第①行。代码第②行是调用reduce()函数,其中lambda
acc, i: acc +
i是进行聚合操做的Lambda表达式,该Lambda表达式有两个参数,其中acc参数是上次累积计算结果,i当前元素,acc
+
i表达式是进行累加。reduce()函数最后的计算结果是一个数值,直接可使用经过reduce()函数返回。代码第行是传入了初始值2,则计算的结果是12。
经过对本章内容的学习,读者能够熟悉在Python中如何定义函数、函数参数和函数返回值,了解函数变量做用域和嵌套函数。最后还介绍了Python中函数式编程基础。
http://www.zhijieketang.com/group/8
做者微博:@tony_关东升br/>邮箱:eorient@sina.com智捷课堂×××公共号:zhijieketangPython读者服务QQ群:628808216