2023-08-11 15:43:34 +08:00
|
|
|
|
---
|
|
|
|
|
title: 函数式编程
|
|
|
|
|
description: Python 函数式编程
|
|
|
|
|
keywords:
|
2023-11-09 17:30:33 +08:00
|
|
|
|
- Python
|
|
|
|
|
- 函数式编程
|
2023-08-11 15:43:34 +08:00
|
|
|
|
tags:
|
2024-10-14 16:48:38 +08:00
|
|
|
|
- FormalSciences/ComputerScience
|
|
|
|
|
- ProgrammingLanguage/Python
|
|
|
|
|
- Python/Advanced
|
2023-08-11 15:43:34 +08:00
|
|
|
|
author: 7Wate
|
|
|
|
|
date: 2023-08-11
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 函数式编程
|
|
|
|
|
|
|
|
|
|
### 函数式编程是什么
|
|
|
|
|
|
|
|
|
|
函数式编程(Functional Programming, FP)是一种编程范式,**主张用数学上的函数方式构建结构和元素之间的关系,而不是改变状态和数据。**在函数式编程中,函数是第一公民,这意味着函数可以被传递、返回和操作,就像其他的数据类型一样。函数在这里是「纯」的,意味着相同的输入始终产生相同的输出,并且没有副作用。
|
|
|
|
|
|
|
|
|
|
```Python
|
|
|
|
|
# 函数作为参数传递给另一个函数
|
|
|
|
|
def apply(func, value):
|
|
|
|
|
return func(value)
|
|
|
|
|
|
|
|
|
|
result = apply(lambda x: x*2, 5) # 输出10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 函数式编程与其他编程范式的区别
|
|
|
|
|
|
|
|
|
|
函数式编程与命令式编程的区别在于,命令式编程关注如何完成任务,强调程序状态和改变状态的语句,而**函数式编程注重数据的映射和组合。**面向对象编程重视对象及其之间的交互,而函数式编程重视函数和数据处理。
|
|
|
|
|
|
|
|
|
|
### 函数式编程的优势和局限性
|
|
|
|
|
|
|
|
|
|
**优势**:
|
|
|
|
|
|
|
|
|
|
- **简洁性**:函数式编程往往更简洁,可以用更少的代码做更多的事情。
|
|
|
|
|
- **可维护性**:由于函数式编程的代码没有副作用,它通常更容易维护和调试。
|
|
|
|
|
- **可重用性**:函数是高度模块化的,可以在多个地方重用。
|
|
|
|
|
|
|
|
|
|
**局限性**:
|
|
|
|
|
|
|
|
|
|
- **内存使用**:由于函数式编程倾向于复制数据而不是改变它,它可能使用更多的内存。
|
|
|
|
|
- **难度**:对于不熟悉该范式的开发者来说,函数式编程可能较难学习。
|
|
|
|
|
|
|
|
|
|
## Python 中的基础函数式工具
|
|
|
|
|
|
|
|
|
|
Python 提供了一些内置的函数式工具,如 `lambda`,`map()`,`filter()` 和 `reduce()`,它们可以帮助你以函数式的方式处理数据。
|
|
|
|
|
|
|
|
|
|
### `lambda`
|
|
|
|
|
|
|
|
|
|
`lambda` 允许我们定义简短的匿名函数。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
double = lambda x: x * 2
|
|
|
|
|
print(double(5)) # 输出10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `map()`
|
|
|
|
|
|
|
|
|
|
`map()` 函数将指定函数应用于序列的每一个元素。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
nums = [1, 2, 3, 4]
|
|
|
|
|
squared = list(map(lambda x: x**2, nums)) # 输出[1, 4, 9, 16]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `filter()`
|
|
|
|
|
|
|
|
|
|
`filter()` 函数根据指定函数的判断结果来过滤序列。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
nums = [1, 2, 3, 4, 5]
|
|
|
|
|
evens = list(filter(lambda x: x % 2 == 0, nums)) # 输出[2, 4]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `reduce()`
|
|
|
|
|
|
|
|
|
|
`reduce()` 函数对序列中的元素进行连续、累计地应用指定函数。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from functools import reduce
|
|
|
|
|
|
|
|
|
|
nums = [1, 2, 3, 4]
|
|
|
|
|
product = reduce(lambda x, y: x*y, nums) # 输出24
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `functools` 模块
|
|
|
|
|
|
|
|
|
|
`functools` 模块提供了一些用于函数式编程的实用工具,如偏函数等。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from functools import partial
|
|
|
|
|
|
|
|
|
|
def multiply(x, y):
|
|
|
|
|
return x * y
|
|
|
|
|
|
|
|
|
|
double = partial(multiply, 2)
|
|
|
|
|
print(double(4)) # 输出8
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 高阶函数
|
|
|
|
|
|
|
|
|
|
### 高阶函是数什么
|
|
|
|
|
|
|
|
|
|
高阶函数接受一个或多个函数作为参数,或者返回一个函数。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
def greet(type_):
|
|
|
|
|
if type_ == 'hello':
|
|
|
|
|
return lambda name: "Hello, " + name
|
|
|
|
|
else:
|
|
|
|
|
return lambda name: "Hi, " + name
|
|
|
|
|
|
|
|
|
|
greeting = greet('hello')
|
|
|
|
|
print(greeting('Alice')) # 输出'Hello, Alice'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 在 Python 中创建和使用高阶函数
|
|
|
|
|
|
|
|
|
|
除了 Python 内置的如 map、filter 和 reduce 这样的高阶函数外,我们也可以创建自己的高阶函数。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
def apply(func, data):
|
|
|
|
|
return [func(item) for item in data]
|
|
|
|
|
|
|
|
|
|
nums = [1, 2, 3, 4]
|
|
|
|
|
result = apply(lambda x: x*2, nums) # 输出[2, 4, 6, 8]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 纯函数和不变性
|
|
|
|
|
|
|
|
|
|
### 纯函数是什么
|
|
|
|
|
|
|
|
|
|
纯函数是函数式编程的核心概念之一。一个函数被认为是纯的,当它满足以下条件时:
|
|
|
|
|
|
|
|
|
|
- **给定相同的输入,总是返回相同的输出。**
|
|
|
|
|
- **没有任何副作用(例如修改全局状态、修改传入的参数、进行 I/O 操作等)。**
|
|
|
|
|
|
|
|
|
|
这些特性使纯函数变得可预测且容易测试。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# 纯函数示例
|
|
|
|
|
def add(x, y):
|
|
|
|
|
return x + y
|
|
|
|
|
|
|
|
|
|
# 不纯的函数示例,因为它改变了外部状态
|
|
|
|
|
counter = 0
|
|
|
|
|
def increment():
|
|
|
|
|
global counter
|
|
|
|
|
counter += 1
|
|
|
|
|
return counter
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 数据的不变性
|
|
|
|
|
|
|
|
|
|
在函数式编程中,数据是不可变的。这意味着一旦一个数据结构被创建,就不能再改变它。而是每次需要修改数据时,都会返回一个新的数据副本。
|
|
|
|
|
|
|
|
|
|
这一特性增加了代码的可读性和可预测性,因为你不必担心数据在不知情的情况下被修改。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# 使用列表作为示例
|
|
|
|
|
lst = [1, 2, 3]
|
|
|
|
|
|
|
|
|
|
# 错误的做法:修改原始列表
|
|
|
|
|
lst.append(4)
|
|
|
|
|
|
|
|
|
|
# 正确的做法:创建新的列表
|
|
|
|
|
new_lst = lst + [4]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 装饰器
|
|
|
|
|
|
|
|
|
|
### Python 中的装饰器
|
|
|
|
|
|
|
|
|
|
装饰器是 Python 中的一个强大工具,它允许开发者**在不修改原始函数代码的情况下,给函数增加新的功能。**它们通常用于日志、权限检查、统计或其他跨越多个函数或方法的通用任务。
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
def my_decorator(func):
|
|
|
|
|
def wrapper():
|
|
|
|
|
print("Something is happening before the function is called.")
|
|
|
|
|
func()
|
|
|
|
|
print("Something is happening after the function is called.")
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
@my_decorator
|
|
|
|
|
def say_hello():
|
|
|
|
|
print("Hello!")
|
|
|
|
|
|
|
|
|
|
say_hello()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 如何利用装饰器优化代码
|
|
|
|
|
|
2023-11-14 11:38:48 +08:00
|
|
|
|
如上所示,装饰器是一个返回另一个函数的函数。要使用装饰器,只需在你想要装饰的函数上方加上 `@decorator_name`。
|
2023-08-11 15:43:34 +08:00
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
def repeat(num):
|
|
|
|
|
def decorator_repeat(func):
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
|
for _ in range(num):
|
|
|
|
|
result = func(*args, **kwargs)
|
|
|
|
|
return result
|
|
|
|
|
return wrapper
|
|
|
|
|
return decorator_repeat
|
|
|
|
|
|
|
|
|
|
@repeat(num=4)
|
|
|
|
|
def greet(name):
|
|
|
|
|
print(f"Hello, {name}")
|
|
|
|
|
|
|
|
|
|
greet("Alice") # 输出四次 "Hello, Alice"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 闭包和自由变量
|
|
|
|
|
|
|
|
|
|
闭包是一种特殊的函数,它可以记住在其所在作用域中声明的自由变量的值,即使它们在函数外部是不可用的。在更简单的语言中,闭包允许函数携带与之相关的数据。
|
|
|
|
|
|
|
|
|
|
**在 Python 中,当内部函数引用了外部函数中的变量,内部函数就被认为是闭包。**
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
def outer_function(x):
|
|
|
|
|
def inner_function(y):
|
|
|
|
|
return x + y
|
|
|
|
|
return inner_function
|
|
|
|
|
|
|
|
|
|
closure = outer_function(10)
|
|
|
|
|
print(closure(5)) # 输出15
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 递归
|
|
|
|
|
|
|
|
|
|
递归是一种编程技巧,其中函数调用自身以解决较小的问题实例。递归通常与某种终止条件结合使用,以防止无限的自我调用。递归函数的经典例子是计算阶乘:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# 此函数会不断调用自己,直到n为1。
|
|
|
|
|
def factorial(n):
|
|
|
|
|
if n == 1:
|
|
|
|
|
return 1
|
|
|
|
|
return n * factorial(n - 1)
|
|
|
|
|
|
|
|
|
|
print(factorial(5)) # 输出120
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 递归、迭代对比
|
|
|
|
|
|
|
|
|
|
| 对比点 | 递归 | 迭代 |
|
|
|
|
|
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------ |
|
|
|
|
|
| **直观性** | 通常更直观和更容易实现 | 通常需要使用循环结构,可能不如递归直观 |
|
|
|
|
|
| **自然选择** | 对于某些问题,如树的遍历,递归是自然的选择 | 对于基本的数据结构,如数组和链表,迭代是自然选择 |
|
|
|
|
|
| **函数调用** | 可能会导致大量的函数调用,从而可能达到调用堆栈的限制 | 由于是循环结构,不会导致函数调用的堆栈溢出 |
|
|
|
|
|
| **效率** | 对于大量的递归,可能不如迭代高效 | 对于简单的循环,迭代可能更加高效 |
|
|
|
|
|
| **内存使用** | 每次调用自己都需要额外的内存来存储变量和信息,可能会导致调用堆栈溢出 | 通常更为内存高效,因为它不需要为每次循环存储额外的信息 |
|
2023-11-14 11:38:48 +08:00
|
|
|
|
| **实现方式** | 函数调用自己,直到满足某个条件 | 使用循环结构,如 `for` 和 `while` |
|