Vanson's Eternal Blog

Python中的函数式编程

Functional in python.jpg
Published on
/18 mins read/---

本文讲解Python的函数式编程。Lambda匿名函数,高阶函数。

什么是函数式编程?

一种编程范式,它将计算视为数学函数的求值,强调使用纯函数来构建程序。

特点

纯函数

纯函数是指输出值完全由输入值决定,并且没有任何可观察的副作用(如修改全局变量、I/O 操作等)。纯函数的行为可以通过输入和输出描述,而不依赖于外部状态。

不可变数据

函数式编程中,数据通常是不可变的。一旦创建,数据结构不能被修改,这有助于避免因共享状态导致的错误。

高阶函数

函数式编程允许将函数作为参数传递,或者返回函数作为结果。这种特性使得代码可以通过组合和嵌套函数来实现复杂的逻辑。

优势

代码简洁且易于理解

函数式代码通常更简洁,更接近问题的数学描述,减少了冗余和复杂的控制流。

易于调试和测试

纯函数的行为只依赖于输入,因此更容易测试和调试。同时,不可变数据减少了因状态变化导致的错误。

并行化和性能优化

纯函数的特性使得并行化和分布式计算更加容易实现,从而提高程序的性能。

高阶抽象

函数式编程提供了强大的抽象能力,可以通过组合和嵌套函数来构建复杂的系统。

Lambda函数

lambda 函数是一个匿名函数(即,没有名称定义),它可以接受任意数量的参数,但与普通函数不同,它只计算并返回一个表达式

定义

格式:lambda p1,[p2,p3,...]:expression

  • 关键字 lambda:与普通函数中 def 类似。
  • 参数:支持传递位置和关键字参数,与普通函数一样,可以是多个参数。
  • 表达式:处理给定参数的表达式。
 
a = 6
print(lambda x: a + 1)
 
# 返回了内存地址
# <function <lambda> at 0x0000017243EC0550>
 

无参数形式

lambda :168

单参数形式

lambda x: x+6

多参数形式

lambda x,y: x*y

带分支形式

lambda x: x-2 if x > 10 else x+2

调用

通过上述的定义,只是得到了一个函数,但需要调用才能得到值。

匿名调用

(lambda函数)(参数) 调用

# 球长方体体积
(lambda x,y,z:x*y*z)(2,3,4)

赋值调用

将匿名函数赋值给一个变量后,再通过变量调用。

func = lambda x,y,z: x*y*z
 
volume = func(2,3,4)

使用场景

多个参数和一个返回值,并且该函数只在一个地方使用,不在其他地方复用,建议使用lambda函数。

高阶函数

高阶函数(Higher-Order Functions)是指可以将其他函数作为参数,或者返回函数作为结果的函数。

函数作为参数

高阶函数可以将其他函数作为参数,这样可以在运行时动态地传递行为。

def apply_function(func, value):
    return func(value)
 
# 定义一个简单的函数
def square(x):
    return x * x
 
# 使用高阶函数
result = apply_function(square, 5)
print(result)  # 输出:25
 

函数作为返回值

高阶函数可以返回另一个函数。这种特性允许我们动态地创建和返回函数,从而实现更灵活的逻辑。

返回一个简单的函数

def create_multiplier(factor):
    def multiplier(x):
        return x * factor
    return multiplier
 
# 创建一个乘以 2 的函数
double = create_multiplier(2)
print(double(5))  # 输出:10
 
# 创建一个乘以 3 的函数
triple = create_multiplier(3)
print(triple(5))  # 输出:15

闭包 是一种特殊的高阶函数,它可以捕获外部变量的状态,即使外部函数已经执行完毕。

def adder(x):
    def inner(y):
        return x + y
    return inner
 
# 创建一个加 10 的函数
add_10 = adder(10)
print(add_10(5))  # 输出:15
 

内置函数

filter()

Python 中的 filter() 函数需要两个参数:

  • 定义过滤条件的函数
  • 函数在其上运行的可迭代对象

为了从过滤器对象中获取一个新的迭代器,并且原始迭代器中的所有项都满足预定义的条件, 需要将过滤器对象传递给 Python 标准库的相应函数:list()、tuple()、set ()、frozenset() 或 sorted()(返回排序列表)

 
my_list = [1, 5, 12, 56, 78, 90]
 
filter(lambda x:x > 10, my_list) # <filter at 0x17243d984c0>
 
# 过滤且排序
sorted(filter(lambda x:x > 10, my_list)) # [12, 56, 78, 90]
 
# 过滤且组成列表
list(filter(lambda x:x > 10, my_list))
 
# 过滤且组成列表
tuple(filter(lambda x:x > 10, my_list))
 
# 过滤且组成 set
set(filter(lambda x:x > 10, my_list))
 
# 过滤且组成 frozenset 不可变的集合
frozenset(filter(lambda x:x > 10, my_list))
 

map()

Python 中的 map() 函数对可迭代的每个项目执行特定操作。 它的语法与 filter() 相同: 一个要执行的函数和一个该函数适用的可迭代对象。

map() 函数返回一个 map 对象,我们可以通过将该对象传递给相应的 Python 函数来从中获取一个新的迭代: list()、tuple()、set()、frozenset() 或 sorted()与 filter() 函数一样,

我们可以从 map 对象中提取与原始类型不同类型的可迭代对象,并将其分配给变量。

map() 和 filter() 函数之间的一个重要区别是第一个函数总是返回与原始函数相同长度的迭代。

my_list = [1,3,5,7,9]
tuple(map(lambda x:x*10, my_list)) # <map object at 0x0000017243D9ACE0>

functool工具包

functools.partial()

用于创建一个部分应用的函数,即提前绑定函数的某些参数,从而减少函数调用时的参数数量。

from functools import partial
 
def add(x, y):
    return x + y
 
# 创建一个部分应用的函数,固定 x=5
add_five = partial(add, 5)
 
print(add_five(3))  # 输出:8
 
functools.reduce()

用于对序列中的元素进行累积操作。对可迭代对象的前两项进行操作并保存结果对保存的结果和可迭代的下一项进行操作以这种方式在值对上进行, 直到所有项目使用可迭代的。

reduce() 函数总是需要一个带有两个参数的 lambda 函数

from functools import reduce
 
numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers)
print(result)  # 输出:15
 
functools.lru_cache()

lru_cache 是一个装饰器,用于缓存函数的结果。它通过缓存最近使用的计算结果来提高函数的性能,避免重复计算。

缓存数据存储在内存中,具体来说,是存储在 Python 的堆内存中。

缓存数据以字典的形式存在,键是函数的输入参数(通常是参数的哈希值),值是函数的返回结果。

from functools import lru_cache
 
@lru_cache(maxsize=32)  # 最大缓存大小为 32
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
 
print(fibonacci(30))  # 输出:832040
 

常用于优化递归函数或计算密集型函数,减少重复计算。

functools.wraps()

wraps 是一个装饰器,用于在定义装饰器时保留原函数的元信息(如名称、文档字符串等)。这有助于调试和代码维护。

from functools import wraps
 
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper
 
@my_decorator
def say_hello(name):
    """Says hello to the given name."""
    print(f"Hello, {name}!")
 
print(say_hello.__name__)  # 输出:say_hello
print(say_hello.__doc__)   # 输出:Says hello to the given name.
 
functools.cmp_to_key()

cmp_to_key 用于将一个比较函数(返回 -1、0 或 1 的函数)转换为一个关键字函数, 适用于 sorted() 和其他需要关键字函数的场景。

用于将旧式的比较函数转换为关键字函数,适用于需要排序的场景。

from functools import cmp_to_key
 
def compare(a, b):
    if a < b:
        return -1
    elif a > b:
        return 1
    else:
        return 0
 
numbers = [5, 2, 9, 1, 5, 6]
sorted_numbers = sorted(numbers, key=cmp_to_key(compare))
print(sorted_numbers)  # 输出:[1, 2, 5, 5, 6, 9]
 
functools.total_ordering()

total_ordering 是一个类装饰器,用于简化类的比较方法的实现。 它允许你只定义 __eq__ 和其中一个比较方法(如 __lt__),然后自动推导出其他比较方法。

用于简化类的比较方法实现,减少重复代码。

 
from functools import total_ordering
 
@total_ordering
class Number:
    def __init__(self, value):
        self.value = value
 
    def __eq__(self, other):
        return self.value == other.value
 
    def __lt__(self, other):
        return self.value < other.value
 
num1 = Number(5)
num2 = Number(10)
print(num1 < num2)  # 输出:True
print(num1 > num2)  # 输出:False
 
functools.singledispatch()

singledispatch 是一个装饰器,用于实现单分派的泛型函数。它允许根据第一个参数的类型动态选择函数实现。

用于实现多态函数,根据参数类型动态选择实现。

from functools import singledispatch
 
@singledispatch
def process(value):
    print(f"Processing unknown type: {value}")
 
@process.register
def _(value: int):
    print(f"Processing integer: {value}")
 
@process.register
def _(value: str):
    print(f"Processing string: {value}")
 
process(42)        # 输出:Processing integer: 42
process("Hello")  # 输出:Processing string: Hello
process(3.14)     # 输出:Processing unknown type: 3.14
 

应用案例

列表推导式

列表推导式(List Comprehension)确实是基于 for 循环的语法糖,用于从现有数据结构(如列表、元组、字典等)生成一个新的列表。

它通过简洁的语法封装了循环和条件逻辑,使得代码更加简洁易读。

[expression for item in iterable if condition]

  • expression:对每个元素进行操作的表达式。
  • item:从可迭代对象中提取的当前元素。
  • iterable:可迭代对象,如列表、元组、字典等。
  • condition(可选):用于过滤元素的条件。

**工作原理 **

  • 遍历可迭代对象:列表推导式首先遍历iterable 中的每个元素。
  • 应用条件(如果有):如果提供了 condition,则对每个元素进行条件判断。只有满足条件的元素才会被处理。
  • 应用表达式:对满足条件的元素应用 expression,生成新的值。
  • 生成新列表:将生成的值添加到新列表中,最终返回这个新列表。
# 案例 1
numbers = [1, 2, 3, 4, 5]
filtered_squares = [x ** 2 for x in numbers if x ** 2 > 10]
print(filtered_squares)  # 输出:[16, 25]
 
# 案例 2
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [x for row in matrix for x in row]
print(flattened)  # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

字典推导式

用于从现有可迭代对象(如列表、元组、字典等)生成一个新的字典。它类似于列表推导式,但生成的是键值对而不是单个值。

{key_expression: value_expression for item in iterable if condition}

  • key_expression:生成字典键的表达式。
  • value_expression:生成字典值的表达式。
  • item:从可迭代对象中提取的当前元素。
  • iterable:可迭代对象,如列表、元组、字典等。
  • condition(可选):用于过滤元素的条件。

**工作原理 **

  • 遍历可迭代对象:字典推导式首先遍历iterable 中的每个元素。
  • 应用条件(如果有):如果提供了 condition,则对每个元素进行条件判断。只有满足条件的元素才会被处理。
  • 应用键和值的表达式:对满足条件的元素分别应用 key_expression 和 value_expression,生成键值对。
  • 生成新字典:将生成的键值对添加到新字典中,最终返回这个新字典。
# 从一个列表生成一个新的字典,只包含偶数的键值对。
numbers = [1, 2, 3, 4, 5]
even_squared_dict = {x: x ** 2 for x in numbers if x % 2 == 0}
print(even_squared_dict)  # 输出:{2: 4, 4: 16}
# 对一个字典的值进行操作,生成一个新的字典。
original_dict = {'a': 1, 'b': 2, 'c': 3}
incremented_dict = {key: value + 1 for key, value in original_dict.items()}
print(incremented_dict)  # 输出:{'a': 2, 'b': 3, 'c': 4}
# 从一个列表生成一个新的字典,键是元素的平方,值是元素的立方。
numbers = [1, 2, 3, 4, 5]
complex_dict = {x ** 2: x ** 3 for x in numbers}
print(complex_dict)  # 输出:{1: 1, 4: 8, 9: 27, 16: 64, 25: 125}
# 从两个列表生成一个新的字典,键是第一个列表的元素,值是第二个列表的元素。
keys = ['a', 'b', 'c']
values = [1, 2, 3]
combined_dict = {key: value for key in keys for value in values}
print(combined_dict)  # 输出:{'a': 3, 'b': 3, 'c': 3}
 

排序

对列表里的字典排序

# 根据字典中键 'b' 的值对列表进行降序排序
l = [{'a': 1, 'b': 41}, {'a': 1111, 'b': 24}, {'a': 1111, 'b': 32}]
l_s = sorted(l, key=lambda x: x["b"], reverse=True)
print(l_s)
 
###### 结果 ######
# [{'a': 1, 'b': 41}, {'a': 1111, 'b': 32}, {'a': 1111, 'b': 24}]
 

对字典的key进行排序

m = {'a': 25, 'c': 27, 'b': 20, 'd': 22}
m_s = sorted(m.items(), key=lambda x: x[0])
print(m_s)
 
###### 结果 ######
# [('a', 25), ('b', 20), ('c', 27), ('d', 22)]
 

对字典的value排序

n = {'a': 25, 'c': 27, 'b': 20, 'd': 22}
n_s = sorted(n.items(), key=lambda x: x[1])
print(n_s)
 
###### 结果 ######
[('b', 20), ('d', 22), ('a', 25), ('c', 27)]
 

多关键字排序

students = [
    {"name": "Alice", "age": 20, "grade": 88},
    {"name": "Bob", "age": 19, "grade": 95},
    {"name": "Charlie", "age": 21, "grade": 85}
]
 
# 按年级降序,年龄升序排序
sorted_students = sorted(students, key=lambda s: (-s['grade'], s['age']))
print(sorted_students)
 

找到多个字典的公共键

dl = [{1: 'life', 2: 'is'}, 
      {1: 'short', 3: 'i'}, 
      {1: 'use', 4: 'python'}]
 
# all() 函数用于检查一个可迭代对象中的所有元素是否都为 True。如果所有元素都为 True,则返回 True,否则返回 False。
result = [k for k in dl[0] if all(map(lambda d: k in d, dl[1:]))]
print(result)  # 输出:[1]
 
###### 理解 ######
# dl[1:]:[{1: 'short', 3: 'i'}, {1: 'use', 4: 'python'}]
# lambda d: k in d, dl[1:]):k是前面的key,<function __main__.<lambda>(d)>, [{1: 'short', 3: 'i'}, {1: 'use', 4: 'python'}]
# all(map(lambda d: 1 in d, dl[1:])):True
 

Lambda函数y优缺点

优点它是评估单个表达式的理想选择,应该只评估一次它可以在定义后立即调用与相应的普通语法相比,它的语法更紧凑它可以作为参数传递给高阶函数,例如 filter()、map() 和 reduce()缺点它不能执行多个表达式它很容易变得麻烦,可读性差,例如当它包括一个 if-elif-...-else 循环它不能包含任何变量赋值(例如,lambda x: x=0 将抛出一个语法错误)我们不能为 lambda 函数提供文档字符串