python 标准库之 functools 模块
functools
模块提供了一系列用于处理函数的工具。其中,像 partial
可以创建一个新的可调用对象,这个对象固定了原函数的部分参数,有点像给函数穿上了“参数防护服”;reduce
能对一个序列进行累积计算,就好比是一个勤劳的小会计,按顺序把序列里的数加起来或者做其他运算;wraps
主要用于装饰器,它能帮助装饰器函数保留被装饰函数的元信息,比如函数名、文档字符串等,让被装饰函数“表里如一”。
底层原理
- partial:
partial
创建新对象的原理是,它会创建一个新的函数对象,这个新函数内部持有原函数的引用,并且记录下固定的参数。当调用新函数时,它会把新传入的参数和之前固定的参数合并起来,一起传递给原函数进行调用。 - reduce:在 Python 3 里,
reduce
已经从全局命名空间移到了functools
模块中。它的底层实现是通过迭代序列,不断地将当前的计算结果和序列中的下一个元素作为参数传递给指定的函数,持续进行累积计算,直到序列遍历完,最终返回累积的结果。 - wraps:
wraps
本质上是一个装饰器,它会将被装饰函数(也就是原函数)的一些属性,如__name__
、__doc__
等,复制到装饰器函数中。这样在使用装饰器后,被装饰函数在外部看起来就像是没有被装饰过一样,保留了原有的标识和文档说明。
细节
- partial:使用
partial
固定参数时,要注意参数的顺序。如果原函数有位置参数和关键字参数,固定位置参数时要按照原函数定义的顺序来。另外,新创建的函数对象的__name__
属性默认是原函数名加上partial
,可以通过手动修改来让它更符合你的需求。 - reduce:
reduce
的第一个参数是一个二元函数(即接受两个参数的函数),如果序列为空,且没有提供初始值,会抛出TypeError
。而当提供了初始值时,reduce
会从初始值开始计算,即使序列为空也能正常返回初始值。 - wraps:在自定义装饰器时,如果不使用
wraps
,可能会导致一些工具(如help()
函数)在查看被装饰函数的文档时出现错误,因为函数的元信息已经被装饰器函数覆盖了。所以,为了保持函数的完整性和可读性,使用wraps
是个好习惯。
使用方式
- partial:假设有一个计算两数之和的函数,现在想固定其中一个参数。
import functools
def add(a, b):
return a + b
add_five = functools.partial(add, 5)
result = add_five(3)
print(result)
- reduce:计算列表中所有数字的乘积。
import functools
def multiply(x, y):
return x * y
nums = [2, 3, 4]
product = functools.reduce(multiply, nums)
print(product)
- wraps:创建一个简单的装饰器,并使用
wraps
保留原函数信息。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def greet(name):
"""这个函数用于打招呼"""
return f"Hello, {name}!"
print(greet.__name__)
print(greet.__doc__)
场景训练
- 表单验证场景:假设有一个验证,函数用于验证输入是否在指定范围内,现在要针对不同的范围创建多个验证函数。
import functools
def validate_in_range(min_val, max_val, value):
return min_val <= value <= max_val
validate_1_to_100 = functools.partial(validate_in_range, 1, 100)
is_valid = validate_1_to_100(50)
print(is_valid)
- 数据聚合场景:在处理一组学生成绩时,计算所有成绩的平均值。
import functools
def sum_scores(acc, score):
return acc + score
scores = [85, 90, 78]
total = functools.reduce(sum_scores, scores, 0)
average = total / len(scores)
print(average)
- 日志记录场景:创建一个装饰器,在函数执行前后记录日志,并保留原函数信息。
import functools
import logging
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"开始执行函数 {func.__name__}")
result = func(*args, **kwargs)
logging.info(f"函数 {func.__name__} 执行结束")
return result
return wrapper
@log_decorator
def process_data(data):
"""处理数据的函数"""
return data * 2
print(process_data.__name__)
print(process_data.__doc__)
代码实践
# partial 代码实践
import functools
def power(base, exponent):
return base ** exponent
square = functools.partial(power, exponent=2)
num = 5
squared_num = square(num)
print(f"{num} 的平方是: {squared_num}")
# reduce 代码实践
import functools
def concatenate_strings(str1, str2):
return str1 + str2
strings = ['Hello, ', 'world!']
concatenated = functools.reduce(concatenate_strings, strings)
print("拼接后的字符串:", concatenated)
# wraps 代码实践
import functools
def timeit_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
import time
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间: {end_time - start_time} 秒")
return result
return wrapper
@timeit_decorator
def slow_function():
"""模拟一个耗时操作"""
import time
time.sleep(2)
return "操作完成"
print(slow_function.__name__)
print(slow_function.__doc__)
result = slow_function()
print(result)