Python中的函数式编程

2025 年 1 月 5 日 | 阅读 21 分钟

在 Python 中,有用的编程包括采用各种技术,因为它是主要的程序添加剂。它侧重于声明式方法,其中程序是通过向数据添加特性而不是不可变状态或负面结果来构建的。

函数式编程是一种创建计算机系统的方法,它侧重于基本的数学表达式。函数式编程与命令式编程不同,它专注于问题本身(“要解决什么”)而不是解决问题的方法(“如何解决”)。

在此框架中,使用表达式而不是语句。另一方面,执行语句是为了为变量赋值。这种方法上的转变使得函数式编程更具声明性,提供了一种清晰简洁的方式来定义程序应该完成什么。

函数式编程概念

函数式编程方言遵循几个基本的思想,这些思想使它们与其他计算机模型不同。让我们深入探讨这些主题。

  • 纯函数:纯函数是函数式编程的关键。这些计算与数学中的函数有两个重要的相似之处。首先,如果给出完全相同的输入,它们总是产生相同的结果。例如,如果你有一个将数字加在一起的算法,用 2 和 3 调用它总是会得到 5。
    这种可预测性使代码更容易理解和测试。其次,纯函数没有副作用。这意味着它们不会修改任何外部状态或变量;它们不会改变全局变量、修改它们的参数或产生任何不是其返回值的输出。这种缺乏副作用可以防止意外行为,使程序更可靠。
  • 递归:与使用“for”或“while”等循环进行新代迭代的命令式编程语言不同,函数式编程依赖于递归。递归是一种函数调用自身来解决相同问题的较小实例的方法。这种方法消除了对传统循环的需求。例如,与其遍历列表以对其元素求和,递归函数会先对第一个元素求和,然后调用自身以对其余元素求和。一旦你习惯了它,这会更直观,并可能导致更优雅的解决方案。
  • 头等函数和高阶函数:在实际编程中,函数是一等公民。这意味着它们可以像任何其他变量一样对待。你可以将函数作为参数传递给其他函数,从函数返回它们,并将它们存储在数据结构中。高阶函数是那些将其他函数作为参数或返回函数作为其结果的函数。此功能允许强大的抽象和代码重用。例如,你可以创建一个函数,该函数接受一个函数和一个列表,将该函数应用于列表的每个元素,并返回结果。然后,这个高阶函数可以与任何函数和列表一起使用,使你的代码更模块化和灵活。
  • 不可变变量:在实际编程中,变量是不可变的。一旦你为变量赋值,它就不能被更改。如果你需要不同的值,你会创建一个新变量。这种不变性防止了许多与更改状态相关的错误,这些错误在其他编程范式中很常见。它还使并发编程更容易,因为没有变量可以被多个线程同时更改。例如,如果你有一个数字列表并想向列表中添加一个数字,你会创建一个带有添加数字的新列表,而不是编辑原始列表。这种方法确保原始数据保持不变,并且可以在程序的各个不同部分安全地共享。

总之,函数式编程提供了一种独特的编写软件的思维方式。通过专注于纯函数、递归、特殊函数和不可变变量,它促进了一种更可预测、可靠和模块化的编码方法。这种范式可以带来更简洁、更易维护的代码,这些代码更容易测试和推断。

纯函数

如前所述,纯函数展现出两个关键特性

  • 对于相同的输入集,它们始终产生相同的输出。例如,3 和 7 的和总是为 10,无论情况如何。
  • 它们不会修改或调整任何输入变量。此属性也称为不变性。纯函数的唯一结果是它计算的值。这些函数是确定性的,这意味着它们的行为是可预测且一致的。

使用函数式编程技术开发的程序在调试方面效率很高。这是因为纯函数没有副作用或隐藏的输入/输出。它们还简化了编写并行或并发程序的过程。当代码以这种方式依赖时,智能编译器可以通过并行化指令、将结果评估推迟到必要时以及由于纯函数的可预测性(它们不会改变,除非输入改变)而缓存结果来优化它。

示例

输出

 
Original List: [5, 6, 7, 8]
Modified List: [25, 36, 49, 64]   

代码解释

此 Python 代码定义了一个名为 `pure_func` 的函数,该函数将列表作为输入,对其进行处理,并返回一个新列表,其中每个元素都已平方。让我们逐步分解代码

  • 函数定义:此行定义了一个名为 `pure_func` 的函数,该函数接受一个参数 `listing`。
  • 创建新列表:在函数内部,创建一个名为 `new_List` 的空列表。这将用于保存输入列表中元素的平方值。
  • 遍历输入列表:此行启动一个 `for` 循环,该循环遍历输入列表中的每个元素。变量 `i` 将一个接一个地获取输入列表中的每个值。
  • 追加平方值:对于输入列表中的每个元素 `i`,`i**2` 计算 `i` 的平方。然后将此平方值追加到 `new_List`。
  • 返回新列表:循环完成后,该函数返回 `new_List`,该列表现在包含输入列表中元素的平方值。
  • 创建原始列表:在函数外部,创建一个名为 `O_List` 的列表并用值 `[5, 6, 7, 8]` 初始化。
  • 调用函数:使用 `O_List` 作为其参数调用 `pure_func` 函数。结果(一个带有平方值的新列表)保存在 `M_List` 中。
  • 打印原始列表和修改后的列表:这些行将原始列表 (`O_List`) 和修改后的列表 (`M_List`) 打印到控制台。输出将显示初始值及其平方对应项。
  • 原始列表:这将显示原始列表 `[5, 6, 7, 8]`。
  • 修改后的列表:这将显示修改后的列表 `[25, 36, 49, 64]`,其中包含原始列表元素的平方。

递归

在函数式编程中,不使用传统的循环(如“for”或“while”)。相反,递归取而代之。递归是一种函数直接或间接调用自身的方式。在递归程序中,我们首先解决最简单的版本,称为基本情况。然后,我们用更小、更简单的问题来表达更大问题的解决方案。你可能会想,基本情况到底是什么?基本情况是告诉程序何时停止递归并退出函数的情况。

示例

输出

 
35   

代码解释

给定的代码定义了一个名为 Sum 的递归函数,它计算列表 l 中从起始索引 j 到特定索引 new 的元素的总和。以下是代码工作方式的逐步解释

  • 函数定义:Sum() 方法接受四个参数
    • l:数字列表
    • j:列表中的当前索引
    • new:列表的长度(或计算总和的索引)
    • c:元素的累积和
  • 基本情况(停止条件):这是递归的基本情况。如果 new 小于或等于 j,则表示我们已到达列表的末尾或所需范围,函数返回累积和 c。
  • 将当前元素添加到累积和:此行将列表 l 中索引 j 处的元素添加到累积和 c。
  • 递归调用:该函数递归调用自身,将索引 j 增加 1 并传递更新后的累积和 c。
  • 返回累积和:最后,该函数返回累积和 c。
  • 初始化和函数调用
    • l 初始化为列表 [5, 6, 7, 8, 9]。
    • C 初始化为零(起始累积和)。
    • New 设置为列表 l 的长度。
    • 调用 Sum 函数,传入 l、0(起始索引)、new(列表长度)和 c(初始累积和)。
    • 打印结果。

函数是头等公民,可以是高阶函数

在编程中,头等对象以统一的方式处理。你可以将它们存储在数据结构中,将它们作为参数传递,或在控制结构中使用它们。如果编程语言像对待其他对象一样对待函数,则它支持头等函数。

以下是头等函数的属性

  • 函数是对象类型的一个示例。
  • 你可以将函数存储在变量中。
  • 你可以将函数作为参数传递给另一个函数。
  • 你可以从另一个函数返回函数。
  • 你可以将函数存储在哈希表、列表等数据结构中。

示例

输出

 
HI, I AM A FUNCTION WHICH IS USED FOR AN EXAMPLE
hi, i am function which is used for an example   

代码解释

  • 函数定义
    • Shouter(t):此函数接受一个字符串 t 作为输入,并使用 upper() 方法返回该字符串的大写版本。
    • W(t):此函数接受一个字符串 t 作为输入,并使用 lower() 方法返回该字符串的小写版本。
  • goat() 函数
    • 此函数将另一个函数作为其参数。
    • 在 goat 函数内部,字符串“Hi, I am a function which is used for an example”作为参数传递给 function。
    • 调用 function 并传入此字符串的结果存储在变量 g 中。
    • 然后打印 g 的值。
  • 调用 goat 函数
    • goat(Shouter):这会调用 goat 函数,并将 Shouter 作为参数。在 goat 内部,字符串“Hi, I am a function which is used for an instance”由 Shouter 转换为大写,并打印结果(“HI, I AM A FUNCTION WHICH IS USED FOR AN EXAMPLE”)。
    • goat(W):这会调用 goat 函数,并将 W 作为参数。在 goat 内部,字符串“Hi, I am a function that's used for an example”由 W 转换为小写,并打印结果(“hello, i'm a function that's used for an instance”)。

内置高阶函数

为了简化对列表和迭代器等可迭代项的处理,Python 包含几个方便的高阶函数。这些函数旨在节省空间,返回迭代器而不是将结果存储在内存中。让我们探讨一些这些内置高阶函数

Map():map() 函数将一个函数和一个可迭代对象(如列表或元组)作为参数。它将给定的函数应用于可迭代对象中的每个项,生成一个结果列表。

语法

参数

  • amusing:一个函数,map 将用于处理可迭代对象中的每个元素。
  • Iter:您需要使用 fun 函数转换的可迭代对象。
  • 返回类型:函数返回 map 类的迭代器,如果需要,您可以将其转换为列表。

例如,如果你有一个数字列表和一个将数字平方的函数,你可以使用 map() 将该函数应用于列表中的每个数字。map() 不会创建包含平方数字的新列表,而是创建一个生成平方数字的迭代器,从而节省内存并提高整体性能。

因此,下次您在 Python 中处理列表或其他可迭代对象时,请记住这些高阶函数可以简化您的生活并提高代码效率。

示例

输出

 
<map object at 0x79372d7361a0>
10 12 14 16   

代码解释

该代码定义了一个函数 `upload`,使用 `map` 函数将其应用于元组中的每个元素,然后打印结果。以下是逐步解释

  • 函数定义:`upload` 函数接受一个参数 `new` 并返回 `new` 加上 `new` 的结果。
  • 元组定义:定义了一个元组 `nums`,其中包含整数 5、6、7 和 8。
  • 映射函数:`map` 函数将 `upload` 函数应用于 `nums` 元组的每个元素。结果 `res` 是一个 `map` 对象,其中包含将 `add` 应用于 `nums` 中每个元素的结果。
  • 打印 map 对象:此行打印 `res` map 对象,因此您现在无法显示单个值,但会显示类似 `` 的内容。
  • 迭代并打印结果:此循环遍历 `res` map 对象中的每个对象并打印它,数字之间用空格分隔。

filter()

filter() 方法通过使用函数测试每个元素是否满足特定条件来帮助您缩短序列。

语法

参数

  • function:此函数检查每个元素是否满足条件。
  • sequence:这是您要过滤的集合。它可以是列表、集合、元组或任何可迭代容器。

返回值

它返回一个只包含通过测试的元素的迭代器。

示例

输出

 
The filtered out letters are:
b
c
d
e
f   

代码解释

此代码演示了如何使用 filter() 函数根据自定义条件从序列中过滤元素。以下是代码的详细解释

  • 定义函数:`function` 函数接受一个输入 `var`。
  • 它定义了一个包含字母 `['b', 'c', 'd', 'e', 'f']` 的列表 `l`。
  • 它测试 `var` 是否在列表 `l` 中。
  • 如果在 `l` 中找到 `var`,则函数返回 `True`。
  • 如果在 `l` 中未找到 `var`,则函数返回 `False`。
  • 定义要过滤的序列,`seq = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']`
  • `seq` 是一个从“a”到“h”的字母列表。
  • 应用 `filter` 函数
    • `filter()` 函数以 `function` 和 `seq` 作为参数调用。
    • 它将 `function` 应用于 `seq` 中的每个元素。
    • `filter()` 函数创建一个迭代器,其中仅包含 `seq` 中 `function` 返回 `True` 的元素。
  • 打印过滤后的字母:此行打印一个标题消息。
  • 遍历过滤后的元素
    • 此循环遍历 `filter` 中的过滤元素。
    • 对于过滤结果中的每个元素 `seq`,它打印 `seq`。

Lambda 函数

在 Python 中,匿名函数是没有名称的函数。通常,我们使用 `def` 关键字定义函数,但对于匿名函数,我们使用 `lambda` 关键字。

语法

以下是关于 lambda 函数的一些关键点

  • 多个参数,单个表达式:lambda 函数可以接受任意数量的参数,但只能有一个表达式。此表达式将被评估并返回。
  • 函数对象:您可以在任何需要函数对象的地方使用 lambda 函数。
  • 单个表达式限制:lambda 函数仅限于单个表达式。它们不能包含多个表达式或语句。
  • 特定用途:lambda 函数在某些编程场景中特别有用,通常在需要简洁函数定义时。

通过了解这些点,您可以有效地在 Python 编程任务中利用 lambda 函数。

示例

输出

 
1000
[8, 10, 12]   

代码解释

让我们逐步分解并解释所提供的 Python 代码

  • Lambda 函数:`c = lambda y: y * y * y` 创建一个匿名函数,该函数接受一个参数 `y` 并返回 `y` 的立方 (`y * y * y`)。
  • Lambda y
    • `y * y * y` 是一种不使用 `def` 关键字编写函数的紧凑方式。
    • `C` 被赋值为此 lambda 函数,因此 `c` 现在是一个函数,可以带参数调用以计算该参数的立方。
  • 调用 Lambda 函数
    • `print(c(10))` 调用带有参数 10 的 lambda 函数 `c`。
    • 该函数计算 10 * 10 * 10,结果为 1000,`print` 输出 1000。
  • 列表定义:`l = [7, 8, 9, 10, 11, 12]` 定义了一个包含整数 7、8、9、10、11 和 12 的列表 `l`。
  • 用于过滤的列表推导式
    • `even = [y for y in l if y % 2 == 0]` 是一个列表推导式。
    • 它遍历列表 `l` 中的每个元素 `y`。
    • 条件 `if y % 2 == 0` 检查 `y` 是否为偶数(即,可被 2 整除而没有余数)。
    • 如果条件为真,则 `y` 包含在新列表 `even` 中。
    • 这导致列表 `even` 只包含原始列表 `l` 中的偶数。
    • 打印结果:`print(even)` 输出列表 `[8, 10, 12]`,这是列表 `l` 中的偶数。

Python 中函数式编程的优势

Python 中的函数式编程提供了多项优势

1. 模块化和可重用性

函数式编程鼓励将任务分解为小的、可重用的函数。这些函数旨在执行特定任务,并且可以轻松组合以创建更复杂的行为。这种模块化使代码更容易理解、维护和随着时间的推移进行扩展。

例如,考虑处理数字列表的实际方法

示例

输出

 
[1, 4, 9, 16, 25]
 [2, 4, 6, 8, 10]   

在这里,`square` 和 `double` 函数是模块化和可重用的,它们可以很容易地使用 `map` 函数组合以处理 `numbers` 列表。

2. 更容易调试和测试

函数式编程强调使用纯函数,这些函数不依赖于可变状态或产生副作用。纯函数对于相同的输入始终返回相同的结果,这使得它们可预测且更易于测试。这种可预测性简化了调试,因为您可以隔离函数的行为而无需担心外部状态变化。

示例

输出

 
12   

这里的 `multiply` 函数是纯粹的——它接受输入 `x` 和 `y` 并返回乘积,而不修改任何外部状态,使其易于测试。

3. 并发和并行

函数式编程鼓励不变性并避免共享可变状态,这对于并发和并行编程至关重要。不可变数据结构和纯函数可以在并行或分布式环境中良好地执行,而没有竞争条件或数据损坏的风险。

示例

输出

 
[1, 4, 9, 16, 25]   

在这里,`square` 函数使用 Python 的 `multiprocessing.Pool` 同时应用于 `numbers` 中的每个元素,这表明有用的编程思想如何指导并行处理。

4. 可读性和可维护性

函数式编程通常导致更具声明性和表达性的代码。对数据结构的操作是使用高级函数(map、filter、reduce)或列表推导式完成的,这可以使代码意图更清晰,并减少对低级生成和控制流构造的需求。

示例

输出

 
 [1, 4, 9, 16, 25]   

这里的列表推导式简洁地表达了将数字转换为平方的转换,从而增强了代码的清晰度和可维护性。

5. 避免副作用

函数式编程不鼓励副作用,例如修改全局变量或外部状态。通过最小化副作用,函数式代码变得更可预测且更易于推断。并且更容易推断。这导致更少的错误和意外行为,尤其是在大型复杂系统中。

示例

输出

 
15 
[1, 2, 3, 4, 5]   

这里的 `calculate_sum` 函数计算数字的总和而不修改原始列表,这演示了避免副作用的原则。

6. 对高阶函数的支持

Python 支持高阶函数,它们可以接受其他函数作为参数或返回函数作为结果。此功能允许强大的抽象,并允许开发人员编写可与不同函数重用的通用算法。

示例

输出

 
8
2   

这里的 `apply_operation` 函数接受 `add` 和 `subtract` 函数作为参数,演示了函数式编程如何支持高阶函数。

7. 适用于数据处理

函数式编程范式非常适合涉及数据处理、转换和分析的任务。列表推导式、生成器和 lambda 函数等函数式构造提供了操作数据结构的简洁有效的方法,使代码更具可读性和可维护性。

示例

输出

 
[1, 4, 9, 16, 25]   

这里的 `map` 函数应用一个 lambda 函数来平方 `numbers` 中的每个元素,展示了函数式编程在数据处理任务中的适用性。

8. 与 Python 生态系统的集成

Python 对函数式编程功能的支持——包括 lambda 函数、生成器和高阶函数——允许开发人员在同一个代码库中利用函数式和面向对象范式。这种灵活性使 Python 成为适用于各种编程模式的多功能语言,并鼓励开发人员为其特定任务选择最合适的方法。

示例

输出

 
['Alice', 'Bob']   

在这种情况下,使用带有 lambda 函数的列表推导式来过滤和提取 25 岁或以上的人员姓名,这演示了 Python 中函数式和面向对象技术的集成。

最后,Python 中的函数式编程提供了各种优势——从模块化和更简单的测试到并发支持和更高的可读性——这可以提高各种软件开发场景中的代码质量和开发人员生产力。

Python 中函数式编程的缺点

Python 中的函数式编程虽然功能强大且富有表现力,但也伴随着某些风险

  1. 性能开销
    • 递归限制:Python 的递归限制和缺乏尾调用优化可能导致深层递归函数出现性能问题和堆栈溢出。
    • 垃圾回收:函数式编程通常会创建许多小型、短命的对象,这可能会给 Python 的垃圾回收器带来压力,从而可能导致性能开销。
  2. 可变性和副作用
    • 内置可变性:Python 的数据结构(列表、字典)默认是可变的,这使得严格遵守函数式编程的不变性原则变得具有挑战性。
    • 意外副作用:确保函数是纯粹的(没有副作用)可能很困难,尤其是在使用可变数据类型和外部状态时。
  3. 缺乏函数式构造
    • 有限的函数式标准库:Python 的标准库缺少一些在 Haskell 或 Scala 等更函数式的语言中发现的高级函数式编程构造和实用程序。
    • 有限的模式匹配:模式匹配是函数式编程中的一个常见功能,在 Python 中受到限制,这可能会降低函数式范式的便利性。
  4. 语法和可读性
    • 冗长的 Lambda 语法:Python 的 lambda 函数语法有限,对于更复杂的操作可能会变得笨拙,从而降低代码的可读性。
    • 函数式构造可读性:使用 map、filter 和 reduce 等高阶函数的代码可能不如等效的命令式代码可读,尤其是对于不熟悉函数式范式的开发人员。
  5. 工具和生态系统
    • 有限的函数式编程工具:与主要为该范式设计的语言相比,Python 生态系统用于函数式编程的工具较少。
    • 较少的社区支持:虽然 Python 拥有一个庞大的社区,但专注于函数式编程的子集较小,这可能会限制函数式编程实践的资源和社区支持的可用性。
  6. 并发和并行
    • 全局解释器锁 (GIL):Python 的 GIL 可能是多线程函数式程序中的瓶颈,使其难以实现真正的并行性。
  7. 学习曲线
    • 陡峭的学习曲线:对于熟悉命令式或面向对象编程的开发人员来说,在 Python 中学习和有效使用函数式编程概念可能具有挑战性。

尽管存在这些缺点,函数式编程在 Python 中对于某些用例仍然非常强大,尤其是在结合 Python 的灵活性以根据需要切换范式时。

Python 中函数式编程的应用

函数式编程 (FP) 是一种编程范式,它将计算视为数学函数的评估,并避免更改状态和可变数据。Python 虽然不是纯粹的函数式语言,但支持函数式编程范式。以下是 Python 中函数式编程的一些应用

1. 数据转换和分析

由于其简洁和富有表现力的语法,FP 特别适用于数据转换和分析。通常使用 map()、filter() 和 reduce() 等函数。

  • Map():将函数应用于输入列表中的所有项。
  • clear out():根据列表的那些元素构建一个列表,其中函数返回 true。
  • lessen():对列表中值的顺序对执行滚动计算。

2. 并行和并发编程

函数式编程可以简化并行和并发编程,因为它避免了可变状态。Python 的并发特性和多进程库可以与 FP 概念结合使用,以有效处理并行性。

FP 非常适合事件驱动和响应式编程,其中状态更改由函数控制。RxPy(Python 的反应式扩展)等库允许开发人员应用 FP 思想来处理异步事件。

3. 声明式编程和管道

FP 允许更具声明性的编程风格,您可以在其中描述您想要实现的目标,而不是如何实现它。这有助于创建用于数据处理或其他工作流的管道。

示例

4. 不变性和纯函数

函数式编程强调不变性和纯函数(没有副作用且对于相同的输入始终产生相同输出的函数)。这可以使代码更可预测且更易于测试。

示例

5. 函数式库和工具

Python 有几个支持函数式编程范式的库

  • functools:提供用于处理函数和可调用对象的高阶函数。
  • Itertools:提供创建迭代器以实现高效循环的函数。
  • Toolz:提供用于函数式编程的实用函数。
  • Fn.Py:实现函数式编程工具。

Python 中的函数式编程可以使代码更简洁、可读性更高且更易于维护,尤其是在数据转换、并行处理和事件驱动编程等领域。通过利用可用的函数式编程工具和库,Python 开发人员可以编写更简洁、更高效的代码。

结论

在 Python 中,函数式编程提供了一种范式,它强调使用纯函数、不变性和高阶函数来促进健壮、可预测和简洁的代码。函数式编程的本质在于将计算视为数学函数的评估,这避免了可变状态和副作用,从而使代码更易于推断和测试。

函数式编程的核心概念之一是使用纯函数。这些函数完全根据其输入产生输出,而不依赖或修改外部状态。这种可预测性使调试更容易并促进并行执行,因为纯函数本质上是线程安全的。

不变性是另一个关键原则,它不鼓励在数据创建后更改其状态。相反,函数式编程鼓励在需要更改时创建新的数据结构。此方法有助于编写线程安全代码并减少由于意外副作用而导致的错误风险。

高阶函数是可以将其他函数作为参数或返回函数作为结果的函数。它们允许强大的抽象,并有助于编写简洁和可重用的代码。map、filter 和 reduce 等函数是 Python 中支持函数式编程范式并鼓励以声明式风格编写代码的重要示例。

Python 尽管是一种多范式语言,但为函数式编程构造提供了强大的支持。它包括用于创建匿名函数的 lambda 表达式、用于简洁新代的列表推导式和用于惰性评估的生成器表达式。functools 等库通过提供用于特征组合、记忆化等工具,进一步增强了 Python 的函数式能力。

总之,虽然 Python 主要以面向对象语言而闻名,但它对函数式编程的支持使开发人员能够编写更简洁、更模块化和更具表现力的代码。通过利用不变性、纯函数和高阶函数等概念,Python 开发人员可以创建更易于维护、测试和推断的应用程序,使函数式编程成为 Python 生态系统中的宝贵工具。