《Python从小白到大牛》第10章 函数式编程

《Python从小白到大牛》已经上市!
《Python从小白到大牛》第10章 函数式编程
程序中反复执行的代码能够封装到一个代码块中,这个代码块模仿了数学中的函数,具备函数名、参数和返回值,这就是程序中的函数。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中可变参数有两种,在参数前加*或**形式,*可变参数在函数中被组装成为一个元组,**可变参数在函数中被组装成为一个字典。

  1. *可变参数

下面看一个示例:

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参数须要关键字参数形式传递。

  1. **可变参数

下面看一个示例:

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所示。

图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

上述代码第①行重构了calculate_fun()函数的定义,如今只接收一个参数opr。代码第②行是在opr

'+'为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体部分不能是一个代码块,不能包含多条语句,只有一条语句,语句会计算一个结果返回给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()。

  1. filter()

过滤操做使用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]
  1. map()

映射操做使用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))

上述代码第①行和第②行实现相同功能。

  1. reduce()

聚合操做会将多个数据聚合起来输出单个数据,聚合操做中最基础的是概括函数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://edu.51cto.com/sd/f907b

配套源代码

http://www.zhijieketang.com/group/8

做者微博:@tony_关东升br/>邮箱:eorient@sina.com智捷课堂×××公共号:zhijieketangPython读者服务QQ群:628808216

相关文章
相关标签/搜索