Python 中的高阶函数和 functools
模块是函数式编程的重要组成部分。
高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。在Python中,函数是一等公民,可以像其他对象一样被传递和使用。
# 定义一个简单的高阶函数
def apply_func(func, value):
"""接收一个函数和一个值,返回函数处理后的结果"""
return func(value)
# 示例函数
def square(x):
return x * x
def double(x):
return x * 2
# 使用高阶函数
result1 = apply_func(square, 4) # 返回16
result2 = apply_func(double, 5) # 返回10
print(f"square(4) = {result1}")
print(f"double(5) = {result2}")
# 使用lambda表达式
result3 = apply_func(lambda x: x + 10, 5) # 返回15
print(f"x + 10 for x=5: {result3}")
# 返回函数的高阶函数
def create_multiplier(factor):
"""返回一个将输入值乘以factor的函数"""
def multiplier(x):
return x * factor
return multiplier
# 创建特定的乘法函数
triple = create_multiplier(3)
print(f"triple(4) = {triple(4)}") # 返回12
Python内置的高阶函数包括map()
, filter()
, sorted()
等。
functools
模块提供了一系列用于操作可调用对象的高阶函数。这些工具特别适合函数式编程风格的开发。
主要功能包括:
reduce()
: 将二元函数累积应用到序列的项目partial()
: 创建偏函数,冻结函数部分参数wraps()
: 用于装饰器,保留被装饰函数的元信息lru_cache()
: 提供缓存功能的装饰器total_ordering()
: 自动生成比较方法的类装饰器singledispatch()
: 基于类型的单分派泛型函数functools.reduce()
函数将一个二元函数(接受两个参数的函数)从左到右依次应用于序列的项,以将该序列归约为单个值。
from functools import reduce
# 计算列表中所有数字的总和
numbers = [1, 2, 3, 4, 5]
sum_result = reduce(lambda x, y: x + y, numbers)
print(f"Sum of {numbers} = {sum_result}") # 输出: 15
# 计算阶乘
def multiply(x, y):
return x * y
factorial = reduce(multiply, range(1, 6)) # 5!
print(f"5! = {factorial}") # 输出: 120
# 提供初始值
# 从10开始,依次减去列表中的每个数
subtraction = reduce(lambda x, y: x - y, [1, 2, 3], 10)
print(f"10-1-2-3 = {subtraction}") # 输出: 4
# 使用reduce实现字符串连接
words = ['Hello', ' ', 'world', '!']
sentence = reduce(lambda x, y: x + y, words)
print(sentence) # 输出: Hello world!
# 找出最大值
max_value = reduce(lambda x, y: x if x > y else y, numbers)
print(f"Max value in {numbers} is {max_value}") # 输出: 5
偏函数允许我们通过固定函数的某些参数来创建一个新函数。这在需要多次调用同一个函数但只有部分参数变化时很有用。
from functools import partial
# 基础函数
def power(base, exponent):
return base ** exponent
# 创建平方函数 - 固定指数为2
square = partial(power, exponent=2)
print(f"square(3) = {square(3)}") # 输出: 9
print(f"square(4) = {square(4)}") # 输出: 16
# 创建立方函数 - 固定指数为3
cube = partial(power, exponent=3)
print(f"cube(3) = {cube(3)}") # 输出: 27
# 固定基数的函数
power_of_two = partial(power, 2)
print(f"2^3 = {power_of_two(3)}") # 输出: 8
print(f"2^4 = {power_of_two(4)}") # 输出: 16
# 在内置函数上使用partial
# 创建一个总是将文本转换为十六进制的函数
hex_converter = partial(int, base=16)
print(hex_converter('a')) # 输出: 10
print(hex_converter('f')) # 输出: 15
print(hex_converter('ff')) # 输出: 255
# 使用partial预设函数参数
def greet(greeting, name):
return f"{greeting}, {name}!"
# 创建特定问候语的函数
hello = partial(greet, "Hello")
goodbye = partial(greet, "Goodbye")
print(hello("Alice")) # 输出: Hello, Alice!
print(goodbye("Bob")) # 输出: Goodbye, Bob!
@functools.wraps()
用于装饰器中,确保被装饰的函数保留其原始信息(如函数名、文档字符串等)。没有它,装饰后的函数会丢失元数据。
from functools import wraps
import time
# 不使用wraps的装饰器
def simple_timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Function {func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
# 使用wraps的装饰器
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Function {func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
# 使用两种装饰器
@simple_timer
def function_without_wraps():
"""这是一个测试函数的文档字符串."""
time.sleep(0.5)
return "Function completed"
@timer
def function_with_wraps():
"""这是一个测试函数的文档字符串."""
time.sleep(0.5)
return "Function completed"
# 查看函数名和文档
print("不使用wraps:")
print(f"函数名: {function_without_wraps.__name__}")
print(f"文档: {function_without_wraps.__doc__}")
print("\n使用wraps:")
print(f"函数名: {function_with_wraps.__name__}")
print(f"文档: {function_with_wraps.__doc__}")
# 调用函数
function_without_wraps()
function_with_wraps()
运行这段代码,你会看到没有使用@wraps
的装饰器会导致函数名变为wrapper
,并且丢失原始文档字符串。
这三个概念是密切相关的:
高阶函数是可以接受函数作为参数或返回函数的函数。
闭包是一个内部函数,它可以访问其外部函数的变量,即使外部函数已经执行完毕。闭包是高阶函数返回函数的一种实现方式。
装饰器是一种特殊的高阶函数,它接受一个函数作为参数并返回一个新函数,通常用于在不修改原始函数代码的情况下扩展其功能。
# 闭包示例
def create_counter(start=0):
"""创建一个简单的计数器函数"""
count = [start] # 使用列表是为了在内部函数中修改值
def counter():
count[0] += 1
return count[0]
return counter # 返回内部函数,形成闭包
# 装饰器示例
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
# 使用装饰器
@log_calls
def add(x, y):
"""加法函数"""
return x + y
# 测试闭包
counter1 = create_counter()
counter2 = create_counter(10)
print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 11
# 测试装饰器
result = add(3, 5)
print(f"Final result: {result}")
# 使用闭包和装饰器创建参数化装饰器
def repeat(times):
"""一个参数化的装饰器,重复执行函数指定次数"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"Hello, {name}!"
print(greet("World")) # 将输出包含3个问候的列表
让我们看看这些概念在实际项目中的应用:
from functools import wraps, partial, reduce, lru_cache
import time
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 使用wraps的装饰器来添加重试逻辑
def retry(max_attempts=3, delay=1):
"""创建一个重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
logger.error(f"Function {func.__name__} failed after {max_attempts} attempts. Error: {e}")
raise
logger.warning(f"Attempt {attempts} failed. Retrying in {delay} seconds...")
time.sleep(delay)
return None
return wrapper
return decorator
# 使用lru_cache进行缓存优化
@lru_cache(maxsize=128)
def fibonacci(n):
"""计算斐波那契数列的第n个数"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 使用partial定制HTTP请求函数
def make_request(method, url, **kwargs):
"""模拟HTTP请求"""
logger.info(f"Making {method} request to {url} with args: {kwargs}")
# 在实际应用中,这里会使用requests库
return f"Response from {method} {url}"
# 创建特定类型的请求函数
get_request = partial(make_request, "GET")
post_request = partial(make_request, "POST")
# 使用自定义装饰器和reduce
@retry(max_attempts=3)
def fetch_and_process_data(urls):
"""获取并处理多个URL的数据"""
# 模拟获取数据
raw_data = [get_request(url) for url in urls]
# 使用reduce处理数据
combined_data = reduce(lambda x, y: x + "\n" + y, raw_data)
return f"Processed data: {combined_data}"
# 测试应用
if __name__ == "__main__":
# 测试fibonacci缓存
start = time.time()
result = fibonacci(35)
end = time.time()
print(f"Fibonacci(35) = {result}, took {end-start:.4f} seconds")
# 查看缓存信息
print(f"Fibonacci cache info: {fibonacci.cache_info()}")
# 测试偏函数
api_url = "https://api.example.com/"
get_users = partial(get_request, api_url + "users")
get_products = partial(get_request, api_url + "products")
print(get_users(param="active"))
print(get_products(id=123))
# 测试重试装饰器和reduce
try:
result = fetch_and_process_data([
"https://example.com/api/data1",
"https://example.com/api/data2"
])
print(result)
except Exception as e:
print(f"Operation failed: {e}")
高阶函数让我们能够将函数作为参数传递或作为返回值,提供了极大的灵活性。
functools.reduce()
允许将二元函数累积应用到序列的元素上,将序列归约为单一值。
functools.partial()
通过固定部分参数创建新函数,简化了代码并提高可读性。
@functools.wraps()
帮助我们在创建装饰器时保留被装饰函数的元信息。
闭包、装饰器和高阶函数相互关联,共同构成了Python函数式编程的基础。