本文主要是介绍Python 轻松应对复杂编程 functools模块速查表,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 文章开篇
- functools简介
- reduce 二元归约
- 1.计算列表中所有元素的和
- 2.计算列表中所有元素的积
- 3.连接列表中所有的字符串
- 4.使用初始值进行累积操作
- partial 偏函数
- partialmethod 偏类实例
- cmp_to_key 转换函数
- cached_property 缓存实例
- lru_cache 通过缓存增加代码性能
- total_ordering 补全比较方法
- singledispatch 分发泛型函数
- 1.单分发机制
- 2.自定义多重分发机制
- wraps 保留元数据
- update_wrapper 更新元数据
- 总结
文章开篇
Python的魅力,犹如星河璀璨,无尽无边;人生苦短、我用Python!
functools简介
functools是Python标准库的一部分,专为高阶函数设计;
高阶函数能操作或返回函数,而functools模块则可将任何可调用对象当作函数处理;
functools 模块常用的函数和装饰器:
函数
- reduce 二元归约
- partial 偏函数
- partialmethod 偏类实例
- cmp_to_key 转换方法
装饰器
- cached_property缓存示例
- lru_cache 通过缓存增加代码性能
- singledispatch 分发泛型函数
- total_ordering 补全比较方法
- wraps 保留元数据
- update_wrapper 更新元数据
reduce 二元归约
用于对一个序列进行归约操作,即对序列中的元素进行累积的二元操作。
reduce函数接受一个二元函数(即接受两个参数的函数)和一个序列,然后对序列中的元素逐个进行二元函数操作,每次操作的结果都会作为下一次操作的第一个参数,直到序列中的所有元素都被处理完毕。原型:reduce(function, iterable[, initializer])
- **function:**含有两个参数的函数,reduce会把这个函数应用到序列的每一个元素上,进行累积操作。
- **iterable:**可迭代对象,如列表、元组等;reduce会对这个可迭代对象中的元素进行累积操作。
- **initializer(可选):**累积操作的初始值。如果提供了这个参数,那么累积操作会从这个初始值开始,而不是从序列的第一个元素开始。
1.计算列表中所有元素的和
from functools import reducenumbers = [1, 2, 3, 4, 5]
sum_numbers = reduce(lambda x, y: x + y, numbers)
print(sum_numbers) # 输出 15
2.计算列表中所有元素的积
from functools import reducenumbers = [1, 2, 3, 4, 5]
product_numbers = reduce(lambda x, y: x * y, numbers)
print(product_numbers) # 输出 120
3.连接列表中所有的字符串
from functools import reducestrings = ['Hello', ' ', 'world', '!']
concatenated_string = reduce(lambda x, y: x + y, strings)
print(concatenated_string) # 输出 'Hello world!'
4.使用初始值进行累积操作
from functools import reducenumbers = [1, 2, 3, 4, 5]
product_of_numbers = reduce(lambda x, y: x * y, numbers, 10)
print(product_of_numbers) # 输出:1200,这里的初始值是10,所以最终的结果是10 * 1 * 2 * 3 * 4 * 5 = 1200
partial 偏函数
偏函数是一种可以将原始函数中的参数“固定”或“冻结”的手段,从而创建一个新的函数,在调用时会使用的“固定”或“冻结”参数值,并且可以继续接受剩余的参数进行调用原始函数。
由 partial() 函数创建的对象有三个只读属性
- func: 是一个可调用对象或者函数,调用 partial 对象时,会将其参数转发给 func;
- args: 是调用 partial 时传入的参数,它将会被传递给 func;
- keywords: 是调用 partial 时传入的关键字参数,它将被传入 func。
from functools import partial# 定义一个普通的函数
def greet(name, greeting, punctuation="!"):return f"{greeting}, {name}{punctuation}"# 使用 functools.partial 创建一个新的函数,其中 'greeting' 参数被预设为 'Hello'
hello_func = partial(greet, greeting='Hello')# 使用新函数,只需要提供 'name' 参数
print(hello_func('Alice')) # 输出: "Hello, Alice!"# 还可以继续为 hello_func 提供额外的参数
print(hello_func('Bob', punctuation="?")) # 输出: "Hello, Bob?"# 使用 functools.partial 创建一个新的函数,其中 'name' 和 'punctuation' 参数都被预设
hello_alice = partial(greet, name='Alice', punctuation="?")# 使用新函数,只需要提供 'greeting' 参数
print(hello_alice('Hi')) # 输出: "Hi, Alice?"
partialmethod 偏类实例
functools.partialmethod 是 Python 标准库中 functools 模块提供的一个较少使用的装饰器;
它允许你创建一个**“部分类实例方法”(partial method);**
一个部分方法类似于一个偏函数(partial function),它是绑定到类实例上的方法,可以预先设置一些参数。
然而,与偏函数不同,部分方法只会在它们被调用时存在,如果没有提供必要的参数,它们可以表现为不存在。
from functools import partialmethodclass Calculator:def __init__(self, number):self.number = number# 使用 partialmethod 预设一个参数add_five = partialmethod(add, 5)# 这是一个普通的方法,用于加法def add(self, other):return self.number + other# 创建 Calculator 实例
calc = Calculator(10)# 调用预设了参数的方法
print(calc.add_five()) # 输出: 15# 调用普通方法,并提供额外的参数
print(calc.add(3)) # 输出: 13# 尝试调用没有预设参数的方法,将抛出 AttributeError
# print(calc.add_three()) # 抛出 AttributeError: 'Calculator' object has no attribute 'add_three'# 你可以动态地添加部分方法
def add_three(self, other=3):return self.add(other)Calculator.add_three = partialmethod(add_three)# 现在可以调用新添加的部分方法
print(calc.add_three()) # 输出: 13
cmp_to_key 转换函数
用于将传统的比较函数(cmp 函数)转换为键函数(key function);
帮助开发者在转换旧代码时保持一致性;
从而使其能够与Python的内建排序函数(如 sorted() 和列表的 sort() 方法)一起使用;比较函数
- 比较函数接受两个参数,并根据比较的结果返回一个结果(负数、零或正数);
- 负数表示第一个参数小于第二个参数,零表示它们相等,正数表示第一个参数大于第二个参数;
- 这种函数通常在Python 2中使用,而在Python 3中,这种传统的比较函数已经被弃用;
键函数
- 键函数接受一个参数,并返回一个用于排序的键。
- 这个键通常是一个可以比较的对象(如数字、字符串等);
- 排序函数会根据这个键来对原始数据进行排序;
- 键函数是Python 3中推荐的方式来自定义排序逻辑;
应用场景
- 旧代码迁移
- 自定义排序
import functools# 定义比较函数
def cmp_func(x, y):if x < y:return -1elif x > y:return 1else:return 0# 使用 cmp_to_key 将比较函数转换为键函数
key_func = functools.cmp_to_key(cmp_func)# 定义一个列表
items = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]# 使用键函数对列表进行排序
sorted_items = sorted(items, key=key_func)print(sorted_items) # 输出: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
cached_property 缓存实例
用于将类的实例方法转换为属性,并且这个属性的值只会被计算一次然后被缓存。
当你再次访问这个属性时,它会直接从缓存中返回之前计算的结果,而不是重新执行方法;
from functools import cached_property
import timeclass ExpensiveCalculation:def __init__(self, data):self.data = dataself._calculated_result = Noneself._calculation_time = None@cached_propertydef calculated_result(self):"""假设这是一个非常耗时的计算"""start_time = time.time()print("计算开始...")time.sleep(5) # 假设这是一个耗时的计算print("计算结束...")self._calculated_result = sum(self.data) # 假设这是计算的结果self._calculation_time = time.time() - start_timeprint(f"执行耗时:{self._calculation_time:.2f} 秒")return self._calculated_result# 使用示例
calc = ExpensiveCalculation([1, 2, 3, 4, 5])# 首次访问 calculated_result,会进行计算并打印耗时
print("第一次执行结果:", calc.calculated_result)
# 第一次调用,控制台打印如下:
# 计算开始...
# 计算结束...
# 执行耗时:5.00 秒
# 第一次执行结果: 15# 等待一段时间,以便更清楚地看到时间差异
time.sleep(2)# 再次访问 calculated_result,将直接从缓存中获取结果,不会重新计算,因此不会打印 "计算开始和计算结束等代码"
start_time = time.time()
print("第二次执行结果:", calc.calculated_result)
# 第二次调用,控制台打印如下:
# 第二次执行结果: 15
# 执行耗时:0.00 秒
end_time = time.time()
print(f"执行耗时:{end_time - start_time:.2f} 秒")
想象一下,你有一个大型数据集,为了分析它,你实现了一个保存整个数据集的类。
此外,你还实现了一些函数来计算诸如手头数据集的标准偏差之类的信息。
问题:你每次调用该方法时,它都会重新计算标准偏差—这需要时间啊!
这就是@cached_property派上用场的地方了
lru_cache 通过缓存增加代码性能
用于实现最近最少使用(Least Recently Used, LRU)缓存策略的装饰器。
它可以将函数的结果缓存起来,以便在下次使用相同的参数调用函数时能够直接从缓存中取出结果,而不是重新计算。
这种机制可以有效地提高函数的执行效率,特别是对于那些计算代价较高、且经常需要重复计算同一组输入结果的函数来说,使用 LRU 缓存可以显著减少计算时间。
LRU 缓存策略是一种常用的缓存替换策略,它总是淘汰最长时间未被使用的缓存项。当缓存达到其容量上限时,最近最少使用的缓存项会被最先淘汰。
from functools import lru_cache
import time@lru_cache(maxsize=128)
def fibonacci(n):"""计算斐波那契数列的第n项"""if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)# 第一次计算斐波那契数列的第30项,并记录时间
start_time = time.time()
result = fibonacci(30)
print(f"Fibonacci(30) = {result}")
print(f"Time taken: {time.time() - start_time} seconds")# 第二次计算斐波那契数列的第30项,由于缓存,这次应该更快
start_time = time.time()
result = fibonacci(30)
print(f"Fibonacci(30) = {result}")
print(f"Time taken: {time.time() - start_time} seconds")# 清除缓存并再次计算,以比较性能
fibonacci.cache_clear()# 第三次计算斐波那契数列的第30项,这次将没有缓存加速
start_time = time.time()
result = fibonacci(30)
print(f"Fibonacci(30) = {result}")
print(f"Time taken: {time.time() - start_time} seconds")
total_ordering 补全比较方法
通过类中提供的一个或多个比较方法,能够自动为你生成缺失的比较方法。
它基于**提供的最少两个个比较方法(通常是_eq_() 或_lt_())**来推导其他比较方法的逻辑。
from functools import total_ordering@total_ordering
class Circle:def __init__(self, radius):self.radius = radiusdef __eq__(self, other):if isinstance(other, Circle):return self.radius == other.radiusreturn Falsedef __lt__(self, other):if isinstance(other, Circle):return self.radius < other.radiusreturn NotImplemented# 使用示例
c1 = Circle(1)
c2 = Circle(2)
c3 = Circle(3)# 自动生成的比较方法
print(c1 < c2) # True
print(c2 <= c3) # True
print(c1 > c3) # False
print(c1 >= c3) # False
print(c1 == c1) # True
print(c1 != c2) # True
singledispatch 分发泛型函数
主要用途是实现泛型编程,即编写能够处理多种数据类型的函数,而无需为每种数据类型编写单独的函数。
通过使用单分发,你可以定义一个通用的函数名,并根据需要为不同的数据类型提供不同的实现。
这有助于保持代码的简洁性和可维护性;
1.单分发机制
rom functools import singledispatch# 使用 singledispatch 装饰器标记一个泛型函数
@singledispatch
def process_data(data):raise NotImplementedError("Unsupported data type")# 为特定类型(例如 str)提供实现
@process_data.register(str)
def _(data):return "Processing string: " + data# 为特定类型(例如 int)提供实现
@process_data.register(int)
def _(data):return "Processing integer: " + str(data)# 为特定类型(例如 list)提供实现
@process_data.register(list)
def _(data):return "Processing list: " + str(data)# 调用泛型函数
print(process_data("Hello")) # 输出: Processing string: Hello
print(process_data(42)) # 输出: Processing integer: 42
print(process_data([1, 2, 3])) # 输出: Processing list: [1, 2, 3]# 尝试使用未注册的类型将引发 NotImplementedError
print(process_data(3.14)) # 抛出: NotImplementedError: Unsupported data type
2.自定义多重分发机制
# 自定义分发机制的基础类
class MultiDispatch:def __init__(self, func):self.func = funcself.registry = {}def register(self, *types):def wrapper(f):self.registry[types] = freturn freturn wrapperdef __call__(self, *args, **kwargs):types = tuple(type(arg) for arg in args)if types in self.registry:return self.registry[types](*args, **kwargs)else:return self.func(*args, **kwargs)# 泛型函数
@MultiDispatch
def process_data(data1, data2):raise NotImplementedError("No implementation for these argument types")# 为 (str, int) 组合提供实现
@process_data.register(str, int)
def _(data1, data2):return "Processing string and integer: " + data1 + " " + str(data2)# 为 (int, str) 组合提供实现
@process_data.register(int, str)
def _(data1, data2):return "Processing integer and string: " + str(data1) + " " + data2# 为 (list, list) 组合提供实现
@process_data.register(list, list)
def _(data1, data2):return "Processing lists: " + str(data1) + " and " + str(data2)# 调用泛型函数
print(process_data("Hello", 42)) # 输出: Processing string and integer: Hello 42
print(process_data(42, "World")) # 输出: Processing integer and string: 42 World
print(process_data([1, 2], [3, 4])) # 输出: Processing lists: [1, 2] and [3, 4]# 尝试使用未注册的类型组合将引发 NotImplementedError
print(process_data(3.14, "Circle")) # 抛出: NotImplementedError: No implementation for these argument types
wraps 保留元数据
用于保留原始函数的名称、文档字符串、注解和模块等属性,在创建新的装饰器函数时非常有用;
当你创建一个装饰器时,通常会定义一个函数,该函数接受一个函数作为参数,并返回一个新的函数。
这样做会导致原始函数的某些属性(如 name、doc 等)丢失,因为它们通常会被新函数的属性所覆盖。
@functools.wraps(func)的作用就是在返回新函数时,将这些属性从原始函数复制到新函数,以保持原始函数的“外观”。
import functoolsdef wraps_decorator(func):# 保留原始函数的名称、文档字符串、注解和模块等属性@functools.wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@wraps_decorator
def person(name: str, age: int, city: str) -> None:"""模拟用户个人介绍函数:param name: 姓名:param age: 年龄:param city: 城市:return: None"""print(f"大家好,我叫{name},今年{age}岁,来自{city},我热爱探索和学习,期待与大家共同进步。")# 输出函数名和文档字符串
print("函数名称:", person.__name__) # 函数名称: person
print("函数文档:", person.__doc__)
# 函数文档:
# 模拟用户个人介绍函数
# :param name: 姓名
# :param age: 年龄
# :param city: 城市
# :return: None
print("函数词典:", person.__dict__) # {'__wrapped__': <function person at 0x7fd8f01b4af0>}
update_wrapper 更新元数据
用于手动执行更新包装函数属性的操作,此函数已不被推荐使用;
在 Python 3.8 及以后的版本中,@functools.wraps(func) 装饰器已经足够使用,并且是推荐的方式来自动更新包装函数的属性。
import functoolsdef my_decorator(func):# 使用wraps可以保留被装饰函数的元数据# @functools.wraps(func)def wrapper(*args, **kwargs):print("Before function call")result = func(*args, **kwargs)print("After function call")return result# 使用update_wrapper可以手动更新被装饰函数的属性# functools.update_wrapper(wrapper, func)return wrapper@my_decorator
def greet(name):"""Say hello to someone."""return f"Hello, {name}!"# 使用装饰器
print(greet.__name__) # 输出: greet
print(greet.__doc__) # 输出: Say hello to someone.
print(greet("World")) # 输出:# Before function call# Hello, World!# After function call
总结
functools模块是Python中一个功能强大的工具集,提供了多种高阶函数和实用工具,如partial用于函数参数预设,total_ordering简化类的排序逻辑,reduce实现序列累积操作等。这些工具能够简洁高效地处理函数和可迭代对象,提升代码的可读性和可维护性,是Python编程中不可或缺的利器。
这篇关于Python 轻松应对复杂编程 functools模块速查表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!