在python中函数是一等公民。Java中则为类是一等公民。
当一个函数将另一个函数作为输入或返回另一个函数作为输出时,这些函数称为高阶函数。
functools模块是Python的标准库的一部分,它是为高阶函数而实现的,用于增强函数功能。
目录
一、@cache装饰器
二、@cached_property装饰器
三、@lru_cache装饰器
四、 partial函数
五、partialmethod函数
六、@singledispatch装饰器
七、@singledispatchmethod装饰器
八、update_wrapper与wraps函数
一、@cache装饰器
它的主要作用是提高函数的执行效率,特别是在函数被多次调用且输入参数相同的情况下,避免重复计算,如果一个函数在相同的输入参数下被多次调用,cache 会记住第一次调用的结果,并在后续调用中直接返回缓存的结果,而不是重新计算。
它是线程安全的,因此可以在多个线程中使用。'
from functools import cache
def factorial(n):
print("无cache执行")
return n
@cache
def factorial_cache(n):
print("执行cache")
return n
if __name__ == '__main__':
for i in range(10):
factorial(10)
factorial_cache(10)
二、@cached_property装饰器
用于将一个类的无参方法转换为缓存属性,意味着该方法在第一次调用时会被计算并缓存结果,后续调用时直接返回缓存的结果。缓存的结果是实例级别的,即每个实例都有自己的缓存。即使后续改变了方法内部涉及的类参数,输出也结果不会发生改变。
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
# 如果类本身存在area属性,则此方法不生效。也就是本身属性读取和写入优先于 cached_property 方法
print("计算面积")
return 3.14159 * self.radius ** 2
if __name__ == '__main__':
# 创建 Circle 对象
circle = Circle(5)
# 第一次访问 area 属性时会计算并缓存结果
print(circle.radius, circle.area)
del circle.radius
circle.radius = 60
# 第二次访问 area 属性时直接返回缓存的结果,即使是中途改变了圆的半径或者是删除了参与计算的参数。
print(circle.radius, circle.area)
三、@lru_cache装饰器
lru_cache相较于cached_property,它装饰的方法是可以带参数的。
缓存是线程安全的,因此可以在 多个线程。
from functools import cached_property, lru_cache
class Circle:
def __init__(self, radius):
self.radius = radius
@lru_cache(maxsize=128) # 缓存最大容量
def area(self, precision):
# 如果类本身存在area属性,则此方法不生效。也就是本身属性读取和写入优先于 cached_property 方法
print("计算面积")
return 3.14159 * self.radius ** precision
if __name__ == '__main__':
# 创建 Circle 对象
circle = Circle(5)
# 第一次访问会计算并缓存结果
print(circle.radius, circle.area(precision=2))
# 第二次访问如果输入参数相同,则直接返回缓存的结果,如果输入参数不同,则重新计算并缓存。
print(circle.radius, circle.area(precision=3))
print(circle.radius, circle.area(precision=3))
print(circle.radius, circle.area(precision=2))
四、 partial函数
它允许你固定一个或多个参数,从而创建一个新的函数,这个新函数在调用时只需要提供剩余的参数。
from functools import partial
# 计算一个数的幂
def power(base, exponent):
return base ** exponent
if __name__ == '__main__':
# 但是我们需要计算是平方,只需要传入具体的数字。所以使用 partial 来简化,产生一个新的函数
square = partial(power, exponent=2)
print(square(3)) # 输出: 9
print(square(4)) # 输出: 16
用于函数组合,即将多个函数组合成一个新的函数。
from functools import partial
# 加函数
def add(a, b):
return a + b
# 积函数
def multiply(a, b):
return a * b
if __name__ == '__main__':
# 对函数进行组合,add函数的入参是固定的。我只需要它的结果作为乘积函数的入参。调用时只需要传入一个参数即可
add_and_multiply = partial(multiply, add(2, 3))
print(add_and_multiply(4)) # 输出: 20
简化回调函数
from functools import partial
def callback(a, b, c):
print(f"a: {a}, b: {b}, c: {c}")
if __name__ == '__main__':
# 使用 partial 固定 a 和 b 参数
fixed_callback = partial(callback, a=1, b=2)
fixed_callback(c=3) # 输出: a: 1, b: 2, c: 3
五、partialmethod函数
它与 partial 类似,但更适合用于类方法,因为它会正确处理 self 参数。
from functools import partialmethod
class Demo:
def __init__(self, value):
self.value = value
def get_something(self, name, age):
return (self.value, name, age)
# 使用 partialmethod 定义一个部分应用的方法
get_something_with_default = partialmethod(get_something, name="长期", age=16)
if __name__ == '__main__':
demo = Demo(value=1)
print(demo.get_something(name=1, age=2))
print(demo.get_something_with_default())
六、@singledispatch装饰器
它用于实现函数的多态性,即根据函数的第一个参数的类型来决定调用哪个具体的实现。singledispatch 允许你为一个函数定义多个实现,每个实现对应不同的参数类型。
一个非常有用的工具,特别是在你需要根据参数类型来选择不同实现的情况下。它使得代码更加清晰和可维护,避免了大量的条件判断。通过结合类型注解,singledispatch 可以进一步提高代码的可读性和可维护性。
from functools import singledispatch
# 定义一个通用的函数
@singledispatch
def process(arg):
return f"Processing generic argument: {arg}"
# 为 int 类型定义一个特定的实现
@process.register
def _(arg: int):
return f"Processing integer: {arg}"
# 为 str 类型定义一个特定的实现
@process.register(str)
def _(arg):
return f"Processing string: {arg}"
# 为 list 类型定义一个特定的实现
@process.register
def _(arg: list):
return f"Processing list: {arg}"
if __name__ == '__main__':
# 调用不同类型的参数
print(process(10)) # 输出: Processing integer: 10
print(process("hello")) # 输出: Processing string: hello
print(process([1, 2, 3])) # 输出: Processing list: [1, 2, 3]
print(process(3.14)) # 输出: Processing generic argument: 3.14
七、@singledispatchmethod装饰器
它与 singledispatch 类似,但专门用于类方法(class methods)
from functools import singledispatchmethod
class Demo:
@singledispatchmethod
def process(self, arg):
return f"Processing generic argument: {arg}"
@process.register
def _(self, arg: int):
return f"Processing integer: {arg}"
@process.register
def _(self, arg: str):
return f"Processing string: {arg}"
@process.register
def _(self, arg: list):
return f"Processing list: {arg}"
if __name__ == '__main__':
# 创建类的实例
demo = Demo()
# 调用不同类型的参数
print(demo.process(10)) # 输出: Processing integer: 10
print(demo.process("hello")) # 输出: Processing string: hello
print(demo.process([1, 2, 3])) # 输出: Processing list: [1, 2, 3]
print(demo.process(3.14)) # 输出: Processing generic argument: 3.14
八、update_wrapper与wraps函数
它用于更新一个包装函数(wrapper function)的元数据,使其看起来更像被包装的原始函数(wrapped function)。这在定义装饰器时非常有用,因为装饰器通常会返回一个新的函数,而这个新函数需要保留原始函数的元数据(如 __name__、__doc__、__module__ 等)
from functools import update_wrapper
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
# 使用 update_wrapper 更新 wrapper 的元数据
update_wrapper(wrapper, func)
return wrapper
@my_decorator
def my_function():
"""This is my function."""
print("Inside my function")
# 调用被装饰的函数
my_function()
# 检查被装饰函数的元数据
print(my_function.__name__) # 输出: my_function
print(my_function.__doc__) # 输出: This is my function.
为了简化代码,functools 模块还提供了一个 wraps 装饰器,它实际上是 update_wrapper 的快捷方式。你可以使用 wraps 装饰器来代替手动调用 update_wrapper。
from functools import wraps
def my_decorator(func):
@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 my_function():
"""This is my function."""
print("Inside my function")
# 调用被装饰的函数
my_function()