11.3 函数式编程

函数式编程属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。在函数式编程中,函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数传入另一个函数,或者作为别的函数的返回值。Python提供了部分的函数式编程。

11.3.1 函数赋值给变量

当定义一个函数的时候,函数名是一个函数类型:

>>> def my_add(a, b):
...     return a + b
...
>>> my_add
<function my_add at 0x000001E4F1FB2EA0>

如果将函数本身赋值给一个变量,那么就可以使用变量来调用这个函数;

>>> f = my_add
>>> f(10, 20)
30

11.3.2 函数作为高阶函数的参数

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

例如:

>>> def my_print(fun, x, y):
...     print(fun(x, y))
...
>>> my_print(f, 10, 20)
30

11.3.2.1 Python中常见的高阶函数

map函数

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,返回一个map对象。

例如:

>>> def f(x):
...     return x ** 2
...
>>> res = map(f, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])       
>>> res
<map object at 0x0000028272DBEF08>
>>> list(res)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

filter函数

filter()函数用于过滤序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如:

>>> def is_even(x):
...     return x % 2 == 0
...
>>> res = filter(is_even, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> res
<filter object at 0x0000028272DD1408>
>>> list(res)
[0, 2, 4, 6, 8]

reduce函数

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。

例如:

>>> from functools import reduce
>>> def f(x, y):
...     return x + y
...
>>> res = reduce(f, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> res
45

sorted函数

Python内置的sorted()函数可以对list进行排序,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

11.3.2.2 匿名函数

lambda语句可以创建一个函数对象。从本质上说,lambda需要一个参数,后跟一个表达式作为函数体,这一表达式执行的值将作为这个函数的返回值。

例如:

>>> f = lambda x: x ** 2
>>> type(f)
<class 'function'>
>>> f
<function <lambda> at 0x0000028272DB99D8>
>>> f(5)
25

这里将lambda语句创建的函数对象赋值给f,f的类型就成为了function,这时可以像使用函数一样使用f,这个函数所作的操作在lambda语句后面说明:x是参数,冒号后面的表达式x ** 2是对参数的操作,这个表达式的值就作为函数的返回值。

这种定义的函数,没有给它起函数名,叫做匿名函数。

匿名函数由于没有名字,不必担心函数名有冲突,在需要的时候,可以将它赋给一个变量,利用变量调用该函数,或者直接将匿名函数作为参数使用。

lambda函数作为参数

可以使用lambda函数改写map函数的使用,例如前面的例子可以改写为:

>>> list(map(lambda x: x ** 2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

11.3.3 函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。也就是说一个函数可以返回一个计算结果,也可以返回一个函数。

例如:11.3-函数返回值.py

# 函数返回值
def my_sum(*args):
    def _sum():
        res = 0
        for x in args:
            res += x
        return res
    return _sum


if __name__ == '__main__':
    f = my_sum(1, 2, 3, 4, 5)
    print(f)

    print(f())

返回的函数并没有立刻执行,而是直到调用了f()才执行。

运行结果为:

<function my_sum.<locals>._sum at 0x000002273BDEE558>  
15

11.3.3.1 装饰器

装饰器(Decorator)是一种特殊的函数,用于动态地给其他函数或类添加额外功能,而无需修改被装饰对象的源代码。例如想要在函数调用前自动打印日志,或者查看某个函数执行的时间,但是又不希望修改这个函数的定义,就可以采用装饰器实现。本质上,装饰器就是一个返回函数的高阶函数。

装饰器通过 @装饰器名称 的语法应用于函数。

装饰器的实现:11.4-装饰器.py

def log_decorator(func):
    """日志装饰器:在函数调用前后打印日志"""
    def wrapper(*args, **kwargs):
        # 调用前的逻辑
        print(f"开始调用函数: {func.__name__}")
        # 调用原函数
        result = func(*args, **kwargs)
        # 调用后的逻辑
        print(f"函数 {func.__name__} 调用结束\n")
        return result
    return wrapper


# 使用装饰器
@log_decorator
def add(a, b):
    return a + b


@log_decorator
def greet(name):
    print(f"Hello, {name}!")


# 调用被装饰的函数
print(add(2, 3))
greet("Alice")

结果为:

开始调用函数: add
函数 add 调用结束

5
开始调用函数: greet
Hello, Alice!
函数 greet 调用结束

装饰器的工作原理:

  • 装饰器 log_decorator 接收一个函数 func 作为参数
  • 定义内部函数 wrapper,包含额外逻辑和对 func 的调用
  • 返回 wrapper 函数替代原函数
  • @log_decorator 语法等于把新定义的函数,当成了装饰器函数的输入参数,等价于 add = log_decorator(add)

带参数的装饰器

装饰器本身也可以接受参数,需要多一层嵌套。

def repeat(num):
    """带参数的装饰器:重复执行函数指定次数"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(num):
                func(*args, **kwargs)
        return wrapper
    return decorator


@repeat(num=3)
def say_hello():
    print("Hello")


say_hello()

结果为:

Hello
Hello
Hello