廖雪峰Python教程学习笔记(4)

楚威
2023-12-01

7. 函数式编程

函数式编程,即 Functional Programming,有以下几个特点:

  • 没有副作用:对于任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数没有副作用;
  • 接近自然语言,易于理解:函数式编程的自由度很高,可以写出很接近自然语言的代码;
  • 函数是“第一等公民”:函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

Python 对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

7.1 高阶函数

高阶函数,即 Higher-order function,有几个特点:

变量可以指向函数

这里以 abs() 函数来作说明:

print(abs(-10)) # 10
# 直接打印 abs 呢?
print(abs) # <built-in function abs>
# 把函数本身赋值给变量
f = abs
print(f) # <built-in function abs>
# 使用变量 f 来调用 abs() 函数
print(f(-100)) # 100

可以看到,可以用变量指向函数,并且可以通过这个变量来调用函数。

函数名也是变量

函数名其实就是指向函数的变量。
通过 abs() 这个内置函数进行说明,这个函数的作用是计算绝对值。这里我们完全可以把 abs 这个函数名看作变量,它指向了一个可以计算绝对值的函数。既然 abs 是一个变量,当然可以指向其他对象。

# 函数名也是变量
# abs = 10
# print(abs(-10)) # 报错
'''
Traceback (most recent call last):
  File "higher_order_function_intro.py", line 13, in <module>
    print(abs(-10))
TypeError: 'int' object is not callable
'''

上面我们把 abs 这个函数名,当做变量,指向了 10。接着,又期望 abs(-10) 可以得出结果 10。实际上,是报错的。其实现在 abs 是指向了 10,而不再指向绝对值函数了。这一点是需要注意的。

传入函数

既然变量可以指向函数,而函数的参数可以接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就是高阶函数

# -----传入函数------
def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs)) # 11

7.1.1 map/reduce

map 函数接收多个参数,第一个是函数,后面的参数是一个或多个Iterable,用逗号隔开,返回一个 Iteratormap 函数语法:

map(function, iterable, …)

map 函数将传入的函数依次作用到Iterable 中的每个元素上,把结果作为 Iterator 返回。map 函数是 Python 中的内置函数。

from collections.abc import Iterator, Iterable
#  map() 函数
# 把 f(x) = x ^ 2, 作用在一个 list [1,2,3,4,5,6,7,8,9] 上
def f(x):
    return x ** 2

r = map(f, [1,2,3,4,5,6,7,8,9])
print(isinstance(r, Iterator)) # True,说明 r 是 Iterator。
print(isinstance(r, Iterable)) # True,说明 r 是 Iterable。
# Iterator 是一个惰性序列
# 迭代方式一:
print(list(r))
# 迭代方式二:
for y in r:
    print(y)

利用 map 函数,把 list 中的每个整数转为字符串:

print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:['1', '2', '3', '4', '5', '6', '7', '8', '9']

利用 map 函数,对两个列表相同位置的元素进行相加

print(list(map(lambda x, y : x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))) # [3, 7, 11, 15, 19]

reduce 函数:第一个参数是函数,第二个参数是 Iterable,第三个参数是可选参数,reduce 函数的返回值与作为第一个参数的函数的返回值是一致的。需要注意的是,作为第一个参数的函数必须接收两个参数。reduce 函数的语法:

reduce(function, sequence [, initial] )

reduce 函数的作用是如果不提供 initial 参数,则先用函数对可迭代序列中的第一个元素和第二个元素进行操作,得到一个结果,再用函数对上一步得到的结果和第三个元素进行操作,以此类推,直到达到最后的结果为止。如果提供 initial 参数,则先用函数对 initial 的值和可迭代序列中的第一个元素进行操作。

reduce 函数并不是 Python 的内置函数,使用 reduce 函数需要先进行导入:

from functools import reduce

计算列表的和:

def add(x, y):
    return x + y
print(reduce(add, [1, 2, 3, 4, 5])) # 15

把序列[1,3,5,7,9]变为整数13579:

def trans(x, y):
    return 10 * x + y
print(reduce(trans, [1, 3, 5, 7, 9])) # 13579

str类型 转 int类型:

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
          '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
    def char2num(ch):
        return DIGITS[ch]
    def fn(x, y):
        return 10 * x + y
    return reduce(fn, map(char2num, s))
print('1378') # 1378

上面的例子涉及了局部函数:Python 支持在函数体内定义函数,这既是局部函数。

7.1.2 filter

filter 函数接收两个参数,第一个参数是函数,第二个参数是可迭代对象,返回值是迭代器对象。

filter 函数是 Python 的内置函数,filter 函数的语法为:

filter(function, iterable)

filter 函数的作用是过滤序列,将序列中的每个元素作为参数传递给函数,函数判断结果是 TrueFalse,为 True 时,则该元素保留在新的列表中,为False 时,则该元素不保留在新的列表中。

需要注意的是,当 function 参数不是 None 时,filter(function,iterable) 和生成器表达式 (item for item in iterable if function(item)) 是等价的;当 function 参数是None 时,等价于 (item for item in iterable if item)

在一个 list 中,删掉偶数,只保留奇数:

# 判断是不是奇数的方法
def is_odd(n):
    return n & 1 != 0
print(list(filter(is_odd, [1, 2, 3, 5, 8, 9, 12]))) # [1, 3, 5, 9]

在一个 list 中,删除负数,只保留整数:

print(list(filter(lambda x : x > 0, [-1, -2, 3, 4, 5]))) # [3, 4, 5]

第一个参数为 None时,保留判断为 True的元素:

print(list(filter(None, [0, 1, '', False, True]))) # [1, True]

7.1.3 sorted

sorted 函数的语法:

sorted(iterable, *, key=None, reverse=False)

第一个参数是一个可迭代对象,
第二个参数是个函数,用来处理进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
第三个参数是排序规则,reverse = True 降序 , reverse = False 升序(默认)。
返回一个新的 list。

参数列表里的 *表示命名关键字参数,意思就是后面两个参数的名字 keyvalue 是被限定的,而且如果传递实参时,必须要带上参数名。

我们还注意到,keyvalue 这两个参数使用了默认参数。

# 对 list 进行从小到大排序
print(sorted([36, -3, 1, 20, -5]))  # [-5, -3, 1, 20, 36]
# 对 list 进行从大到小排序
print(sorted([36, -3, 1, 20, -5], reverse=True))  # [36, 20, 1, -3, -5]
# 对 list 按绝对值从小到大排序
print(sorted([36, -3, 1, 20, -5], key=abs))  # [1, -3, -5, 20, 36]

7.2 返回函数

函数作为返回值

以求和函数为例说明:

# 函数作为返回值,不会立即求和
def lazy_sum(*args):
	# 这里定义了局部函数
    def sum():
        result = 0
        for n in args:
            result += n
        return result
    return sum

f = lazy_sum(1, 2, 3, 4)
print(f) # <function lazy_sum.<locals>.sum at 0x7f51b0fd28c8>
print(f()) # 10

上面的代码使用了闭包,那么什么是闭包呢?

一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包

因为在上面的代码中,函数 sum 就是一个闭包,它引用了 lazy_sum 的参数 args

每次调用 lazy_sum,都会返回一个新的函数,即便是传入的参数是一模一样的:

# 每次调用 lazy_sum ,都会返回一个新的函数变量
f1 = lazy_sum(1, 2, 3, 4) 
f2 = lazy_sum(1, 2, 3, 4)
print(f1) # <function lazy_sum.<locals>.sum at 0x7f8c919ea950>
print(f2) # <function lazy_sum.<locals>.sum at 0x7f8c919ea9d8>

但是, 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

7.3 匿名函数

以之前学习 map 函数时,把容器中的数字,经过 f(x) = x^2,变换每一个元素,得到新的容器的例子来说明:

之前的写法是:

def f(x):
    return x ** 2
print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

匿名函数的写法是:

print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

可以看到我们使用 lambda x: x * x 替代了函数 f

关键字 lambda表示匿名函数,冒号前面的 x 表示函数参数,和函数一样,参数列表可以包含多个参数,也可以不包括任何参数;冒号后面的 x * x 表示函数体,但是匿名函数的函数体只能有一个表达式,并且不用写return,返回值就是这个表达式的结果。

匿名函数也是函数,而函数可以赋值给变量,所以匿名函数也可以赋值给变量。

g = lambda x, y: x + y
print(g(1, 1)) # 2

匿名函数也可以作为返回值返回。

def add(x, y):
    return lambda: x + y

print(add(2, 3)()) # 5

注意上面的例子中,匿名函数的参数列表为空,但是冒号可以不能省略的。

7.4 装饰器

装饰器这一部分有点不好懂。
Python 中的装饰器(decorator)允许在不修改原函数定义的情况下,在代码允许期间动态添加功能。这样的好处是,抽离出大量与函数功能本身无关的代码到装饰器中并获得重用。

参考:理解 Python 装饰器看这一篇就够了

7.5 偏函数

偏函数(partial function),是由 Python 的 functools 模块提供的功能。

偏函数的作用是:当函数的参数个数太多,需要简化调用时,可以使用 functools.partial 创建一个新的函数,新函数可以固定住原函数的部分参数,从而在调用时更简单。

int 函数把字符串转为整数的例子来说明:

大量需要一个把字符串,按二进制转为十进制的整数,可以这样写:

print(int('100000', base = 2)) # 32,把 100000 按照二进制,转为十进制是32
print(int('10000', base = 2)) # 16,把 10000 按照二进制,转为十进制是16
print(int('1000', base = 2)) # 8,把 1000 按照二进制,转为十进制是8

抽取一个函数 int2,是这样的:

def int2(x, base=2):
    return int(x, base)

print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8

使用偏函数,需要先导入 import functools

int2 = functools.partial(int, base = 2)
print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8
print(int2('100000',  base = 10)) # 100000
 类似资料: