器→工具, 编程语言

Python标准库学习之functools

钱魏Way · · 3 次浏览

functools 是 Python 标准库中的一个模块,专门为高阶函数和可操作或返回函数的工具提供支持。这个模块非常适合函数式编程风格的开发者,因为它提供了多种用于操作函数的功能。

functools.partial与functools.partialmethod

functools.partial

functools.partial 是 Python 标准库中的一个强大工具,用于创建部分应用函数。它允许你固定函数的一些参数,从而生成一个新的函数对象,这个新函数可以在调用时省略那些已固定的参数。这在需要多次调用同一函数并且每次调用时有相同的参数时特别有用。

基本概念

  • 部分应用:partial通过固定函数的部分参数,创建一个新的函数对象。这个过程称为“部分应用”。
  • 参数固定:你可以固定位置参数和关键字参数。

使用方法

partial 可以直接应用于任何可调用对象(如函数、方法等),并生成一个新的函数对象。

from functools import partial

def multiply(x, y):
    return x * y

# 创建一个新的函数,固定 multiply 的第一个参数为 2
double = partial(multiply, 2)

# 调用新函数,只需要传递第二个参数
result = double(5)  # 等同于 multiply(2, 5)
print(result)  # 输出: 10

参数

  • func:需要固定参数的可调用对象。
  • *args:需要固定的位置参数。
  • **keywords:需要固定的关键字参数。

特性和注意事项

  • 提高代码重用性:通过部分应用,你可以创建特定用途的函数,而不需要重复编写相同的参数传递逻辑。
  • 简化回调函数:在某些框架中,可能需要提供回调函数,partial 可以用于固定某些参数以简化代码。
  • 参数覆盖:在调用 partial 创建的函数时,可以通过传递参数来覆盖已固定的参数。
  • 与方法结合:partial 可以用于绑定实例方法,这样在调用时可以省去实例对象的传递。
  • 不影响原函数:partial 创建的新函数不会影响原函数的行为和属性。

functools.partialmethod

functools.partialmethod 是 Python 标准库中的一个工具,类似于 functools.partial,但它专门用于方法的部分应用。partialmethod 允许你在类中定义方法时预先设置部分参数,从而创建一个新的方法。这在需要为类方法提供默认参数或简化方法调用时特别有用。

基本概念

  • 部分应用:partialmethod允许你为一个方法预先指定部分参数,这样在调用方法时可以省略这些参数。
  • 方法绑定:与partial 不同,partialmethod 专门用于类的方法,因此会正确处理方法绑定问题。

使用方法

partialmethod 通常用于类定义中,可以通过将它作为方法的装饰器来应用。以下是一个简单的示例:

from functools import partialmethod

class MyClass:
    def greet(self, name, greeting):
        return f"{greeting}, {name}!"

    # 使用partialmethod为greet方法预先设定greeting参数
    greet_hello = partialmethod(greet, greeting="Hello")
    greet_hi = partialmethod(greet, greeting="Hi")

# 示例调用
obj = MyClass()
print(obj.greet_hello("Alice"))  # 输出: Hello, Alice!
print(obj.greet_hi("Bob"))       # 输出: Hi, Bob!

详细说明

  • 定义方法:在类中定义一个方法,然后使用partialmethod 为该方法创建部分应用版本。
  • 预设参数:通过partialmethod 可以预设方法的某些参数,类似于 partial。
  • 方法绑定:由于partialmethod 是为类方法设计的,因此它会正确处理实例和类的绑定问题。

参数

  • func:要进行部分应用的方法。
  • *args:要预设的参数。
  • **keywords:要预设的关键字参数。

特性和注意事项

  • 适用于方法:partialmethod 专门用于类的方法,与 partial 的功能类似,但专注于方法绑定。
  • 增强可读性:通过为方法提供默认参数,partialmethod 可以简化方法调用,提高代码的可读性。
  • 灵活性:可以为同一个方法创建多个部分应用版本,每个版本预设不同的参数。
  • 绑定问题:partialmethod 解决了 partial 在方法绑定时可能出现的问题,因为 partial 直接用于方法时会丢失绑定信息。

functools.update_wrapper 和 functools.wraps

functools.update_wrapper 和 functools.wraps 是 Python functools 模块中的两个实用工具,主要用于装饰器的实现。这两个工具帮助保留被装饰函数的元数据(如函数名、文档字符串等),以便在装饰后,函数的这些属性不会丢失。

  • update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):用于手动更新包装器函数的元数据。
  • wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):作为装饰器应用于包装器函数,简化了 update_wrapper 的使用。

functools.update_wrapper

functools.update_wrapper 是一个函数,用于将一个装饰器的元数据更新为被装饰函数的元数据。

参数:

  • wrapper: 装饰器函数,即包装函数。
  • wrapped: 被装饰的函数。
  • assigned: 需要复制的属性元组,默认是WRAPPER_ASSIGNMENTS,包括 __module__, __name__, __qualname__, __annotations__ 和 __doc__。
  • updated: 需要更新的属性元组,默认是WRAPPER_UPDATES,包括 __dict__。

用法:

from functools import update_wrapper

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Function is being called")
        return func(*args, **kwargs)
    
    # 更新包装函数的元数据
    update_wrapper(wrapper, func)
    return wrapper

@my_decorator
def example_function():
    """This is an example function."""
    print("Example function executed.")

print(example_function.__name__)  # 输出: example_function
print(example_function.__doc__)   # 输出: This is an example function.

functools.wraps

functools.wraps 是一个装饰器工厂,它返回一个装饰器,该装饰器应用 update_wrapper 更新包装函数的元数据。它是 update_wrapper 的一个简化使用方式,通常用于装饰器的实现中。

参数

  • wrapped: 被装饰的函数。
  • assigned: 与update_wrapper 相同。
  • updated: 与update_wrapper 相同。

wraps 的用法非常简单,通常用于定义装饰器时,直接应用于包装函数。

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Function is being called")
        return func(*args, **kwargs)
    
    return wrapper

@my_decorator
def example_function():
    """This is an example function."""
    print("Example function executed.")

print(example_function.__name__)  # 输出: example_function
print(example_function.__doc__)   # 输出: This is an example function.

functools.singledispatch与functools.partialmethod

functools.singledispatch

functools.singledispatch 是 Python 标准库中的一个功能强大的装饰器,用于实现基于单一参数类型的泛型函数调度。它允许你为不同类型的参数定义不同的函数实现,从而简化多态函数的编写。

singledispatch 是一种单分派泛型函数装饰器,这意味着它根据函数的第一个参数的类型来选择适当的函数实现。通过这种方式,你可以在同一个函数名下实现不同类型参数的不同处理逻辑。

要使用 singledispatch,你首先需要定义一个通用函数(称为基函数),并用 @singledispatch 装饰。然后,你可以使用 register 方法为不同的类型注册具体的实现。

from functools import singledispatch

@singledispatch
def process(data):
    raise NotImplementedError("Unsupported type")

@process.register(int)
def _(data):
    print(f"Processing integer: {data}")

@process.register(str)
def _(data):
    print(f"Processing string: {data}")

@process.register(list)
def _(data):
    print(f"Processing list: {data}")

# 示例调用
process(10)      # 输出: Processing integer: 10
process("hello") # 输出: Processing string: hello
process([1, 2, 3]) # 输出: Processing list: [1, 2, 3]

详细说明

  • 基函数:这是一个用@singledispatch 装饰的函数,它定义了默认行为。如果没有为特定类型注册实现,就会调用这个默认函数。
  • 注册函数:使用register 方法为特定类型注册实现。每个注册函数都应该接受与基函数相同数量的参数。
  • 匿名函数:在注册函数时,通常使用匿名函数名_,因为函数名并不重要,重要的是类型。

特性和注意事项

  • 多态调度:singledispatch 仅支持基于第一个参数的类型进行调度。
  • 继承支持:如果没有为某个类型显式注册实现,singledispatch 会自动查找参数的基类类型是否有注册实现。
  • 灵活性:可以在运行时动态添加新的类型实现。
  • 可读性:使代码更具可读性和可维护性,因为你可以在一个地方集中管理不同类型的处理逻辑。
  • 性能:虽然 singledispatch 提供了灵活性和易用性,但它可能在某些情况下稍微影响性能,特别是当涉及大量类型注册时。

functools.partialmethod

functools.partialmethod 是 Python 标准库中的一个工具,类似于 functools.partial,但它专门用于方法的部分应用。partialmethod 允许你在类中定义方法时预先设置部分参数,从而创建一个新的方法。这在需要为类方法提供默认参数或简化方法调用时特别有用。

基本概念

  • 部分应用:partialmethod允许你为一个方法预先指定部分参数,这样在调用方法时可以省略这些参数。
  • 方法绑定:与partial 不同,partialmethod 专门用于类的方法,因此会正确处理方法绑定问题。

使用方法

partialmethod 通常用于类定义中,可以通过将它作为方法的装饰器来应用。以下是一个简单的示例:

from functools import partialmethod

class MyClass:
    def greet(self, name, greeting):
        return f"{greeting}, {name}!"

    # 使用partialmethod为greet方法预先设定greeting参数
    greet_hello = partialmethod(greet, greeting="Hello")
    greet_hi = partialmethod(greet, greeting="Hi")

# 示例调用
obj = MyClass()
print(obj.greet_hello("Alice"))  # 输出: Hello, Alice!
print(obj.greet_hi("Bob"))       # 输出: Hi, Bob!

详细说明

  • 定义方法:在类中定义一个方法,然后使用partialmethod 为该方法创建部分应用版本。
  • 预设参数:通过partialmethod 可以预设方法的某些参数,类似于 partial。
  • 方法绑定:由于partialmethod 是为类方法设计的,因此它会正确处理实例和类的绑定问题。

参数

  • func:要进行部分应用的方法。
  • *args:要预设的参数。
  • **keywords:要预设的关键字参数。

特性和注意事项

  • 适用于方法:partialmethod 专门用于类的方法,与 partial 的功能类似,但专注于方法绑定。
  • 增强可读性:通过为方法提供默认参数,partialmethod 可以简化方法调用,提高代码的可读性。
  • 灵活性:可以为同一个方法创建多个部分应用版本,每个版本预设不同的参数。
  • 绑定问题:partialmethod 解决了 partial 在方法绑定时可能出现的问题,因为 partial 直接用于方法时会丢失绑定信息。

functools.lru_cache与functools.cache

functools.lru_cache

functools.lru_cache 是 Python 标准库中一个非常强大的工具,用于实现函数结果的缓存(memoization)。它可以显著提高函数的性能,尤其是在函数调用频繁且输入参数重复的情况下。lru_cache 是基于 LRU(Least Recently Used,最近最少使用)策略实现的缓存机制。

基本概念:

  • 缓存:lru_cache会存储函数的输入参数及其对应的输出结果。当函数被再次调用时,如果参数相同,lru_cache 会直接返回缓存中的结果,而不需要重新计算。
  • LRU策略:当缓存达到设定的最大容量时,lru_cache会移除最近最少使用的条目以腾出空间。

lru_cache 是一个装饰器,可以直接应用于需要缓存的函数。

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # 输出: 55

参数

  • maxsize:指定缓存的最大容量。默认值是 128。如果设置为None,缓存将无限制增长(不建议这样做,因为这可能导致内存问题)。
  • typed:如果设置为True,则不同参数类型的调用将被分别缓存。例如,f(3) 和 f(3.0) 会被视为不同的调用。

特性

  • 提高性能:对于需要重复计算且输入相同的函数,lru_cache可以显著减少计算时间。
  • 透明性:lru_cache会保留被装饰函数的签名和文档字符串。
  • 线程安全:lru_cache是线程安全的,可以在多线程环境中使用。

你可以通过访问 cache_info() 方法来查看缓存的命中率和使用情况。

from functools import lru_cache

@lru_cache(maxsize=3)
def add(a, b):
    return a + b

add(1, 2)
add(2, 3)
add(3, 4)
add(1, 2)  # 这次调用将命中缓存

print(add.cache_info())  # 输出: CacheInfo(hits=1, misses=3, maxsize=3, currsize=3)

可以通过调用 cache_clear() 方法来清除缓存。

add.cache_clear()
print(add.cache_info())  # 输出: CacheInfo(hits=0, misses=0, maxsize=3, currsize=0)

注意事项

  • lru_cache适用于纯函数(相同输入总是产生相同输出,且没有副作用)。
  • 使用lru_cache 会增加内存消耗,因为需要存储输入参数和输出结果。
  • 在内存有限的环境中,应该谨慎设置maxsize 参数。

functools.lru_cache 是一个非常有用的工具,特别是在需要优化递归算法或其他计算密集型任务时。通过合理设置缓存大小和了解函数的使用模式,可以显著提高程序的性能。

functools.cache

functools.cache 是 Python 3.9 中引入的一个装饰器,用于实现函数的简单缓存机制。它与 functools.lru_cache 类似,但更为简单,主要用于缓存那些纯函数(给定相同输入总是返回相同输出,没有副作用)的结果。

基本概念

  • 缓存:cache装饰器会将函数的输入参数和对应的输出结果存储在内存中。这样,当函数再次被调用时,如果参数相同,就可以直接返回缓存的结果,而不需要重新计算。
  • 无限缓存:与lru_cache 不同,cache 没有最大缓存大小的限制,会一直缓存所有结果,直到程序终止或缓存被手动清除。

使用方法

要使用 functools.cache,只需将其作为装饰器应用于目标函数即可:

from functools import cache

@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # 输出: 55

特性和注意事项

  • 简单易用:cache 提供了一种非常简单的方式来缓存函数结果,适用于不需要复杂缓存管理的场景。
  • 无限增长:cache 不限制缓存的大小,因此可能导致内存使用过多。在需要限制缓存大小的情况下,应该使用 lru_cache。
  • 适用于纯函数:cache 最适合用于那些没有副作用的纯函数。如果函数的输出可能会因外部状态的改变而不同,则不应使用 cache。
  • 线程安全:与 lru_cache 类似,cache 也是线程安全的,可以在多线程环境中使用。
  • 手动清除缓存:可以通过调用 cache_clear() 方法来清除缓存。

functools.cache_property

functools.cached_property 是 Python 3.8 中引入的一个装饰器,用于将类的方法转换为缓存属性。这意味着属性值会在首次访问时计算并缓存,以后访问时直接返回缓存的值,而不再重新计算。这对于那些计算代价高昂且结果不变的属性非常有用。

基本概念

  • 缓存属性:cached_property将一个方法的结果缓存为属性,以后访问时不再重新调用方法,而是直接返回缓存的结果。
  • 惰性求值:属性值在首次访问时才计算,而不是在对象实例化时。

使用方法

要使用 cached_property,你需要将其作为装饰器应用于类中的方法。这个方法应只接受 self 作为参数,并返回你希望缓存的结果。

from functools import cached_property

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @cached_property
    def area(self):
        print("Calculating area...")
        return 3.14159 * self.radius * self.radius

# 示例
c = Circle(10)
print(c.area)  # 输出: Calculating area... 314.159
print(c.area)  # 输出: 314.159

在上述示例中,area 属性在第一次访问时计算并缓存,后续访问直接返回缓存的值,而不会再次打印 “Calculating area…”。

特性和注意事项

  • 只读属性:cached_property 创建的属性是只读的。试图为该属性赋值会导致 AttributeError。
  • 线程安全:cached_property 是线程安全的,这意味着它可以在多线程环境中安全使用。
  • 与 property 类似:cached_property 的行为类似于 property,但它通过缓存机制避免了重复计算。
  • 缓存失效:cached_property 不支持缓存失效机制。如果对象的状态改变需要重新计算属性值,必须手动删除缓存的属性(例如,通过 del obj.attr)。
  • 内存使用:由于属性值会一直缓存,因此在内存敏感的应用中使用时需谨慎。

functools.total_ordering

functools.total_ordering 是 Python 标准库中的一个装饰器,用于简化实现类的比较方法。通常,当你定义一个类并希望其实例可以进行排序时,你需要实现所有六种比较方法:__lt__、__le__、__eq__、__ne__、__gt__ 和 __ge__。这可能会导致重复代码。total_ordering 通过只要求你实现其中一个或两个基本方法(通常是 __lt__ 和 __eq__),然后自动生成其余的比较方法,从而简化了这个过程。

基本概念

  • 简化实现:通过实现一个或两个基本比较方法,total_ordering自动生成其余的比较方法。
  • 减少重复:避免在类中重复实现多个类似的比较方法。

使用方法

要使用 total_ordering,你需要在类中至少定义 __eq__ 和一个其他比较方法(例如 __lt__、__le__、__gt__、__ge__),然后将 total_ordering 装饰器应用于该类。

from functools import total_ordering

@total_ordering
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.age == other.age

    def __lt__(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.age < other.age

# 示例
alice = Person('Alice', 30)
bob = Person('Bob', 25)

print(alice > bob)  # 输出: True
print(alice >= bob) # 输出: True
print(alice < bob)  # 输出: False

在上述示例中,Person 类只定义了 __eq__ 和 __lt__,其余的比较方法由 total_ordering 自动生成。

特性和注意事项

  • 实现要求:必须实现 __eq__ 和至少一个其他比较方法。total_ordering 需要这些方法来推导其他的比较运算。
  • 性能考虑:自动生成的方法可能不如手动实现的高效,尤其是在需要大量比较操作时。因此,在性能关键的应用中,手动实现所有方法可能更为合适。
  • 类型检查:在实现比较方法时,建议进行类型检查(如 isinstance),以确保与不兼容类型的比较返回 NotImplemented。
  • 用途限制:total_ordering 主要用于需要排序或比较的场景,对于不需要这些功能的类,使用它是多余的。

functools.reduce

reduce() 函数最初是在 Python 的内置命名空间中可用的,然而,从 Python 3 开始,它被移到了 functools 模块中。这一变化的原因主要与 Python 语言设计哲学和代码可读性有关。以下是一些关键原因:

  • 可读性和明确性
    • Python 之禅:Python 的设计哲学之一是“明确优于隐式”(Explicit is better than implicit)。reduce()的功能对于初学者来说可能不太直观,因为它涉及到累积计算和函数式编程的概念。
    • 代码可读性:reduce()在某些情况下可能会使代码难以理解,特别是对于不熟悉函数式编程的人。通过将其移到 functools 模块中,强调了它的特殊用途,提示开发者在使用前应理解其工作原理。
  • 使用频率
    • 较低的使用频率:与其他内置函数相比,reduce()的使用频率较低。常见的集合操作,如迭代、过滤和映射,通常可以通过 for 循环、列表推导式或 map() 和 filter() 等更直观的工具来实现。
    • 推荐使用其他工具:在很多情况下,使用循环或列表推导式可以更清晰地表达逻辑,而不是通过reduce()。
  • 提升模块化
    • 模块化设计:将reduce() 移到 functools 中,有助于保持 Python 内置命名空间的简洁性,并鼓励开发者使用更模块化的方式来访问不常用的功能。
    • 功能聚合:functools模块聚合了许多与函数式编程相关的工具,如 partial、lru_cache 和 singledispatch。将 reduce() 移到 functools 中,与这些工具形成了更自然的组合。

使用示例

reduce() 的主要功能是对一个序列进行累积计算。以下是一个简单的示例,展示如何使用 reduce() 来计算一个列表中所有元素的乘积:

from functools import reduce

def multiply(x, y):
    return x * y

numbers = [1, 2, 3, 4, 5]
result = reduce(multiply, numbers)
print(result)  # 输出: 120

functools.cmp_to_key

functools.cmp_to_key 是 Python 标准库中的一个工具,用于将老式的比较函数(基于 cmp 函数的比较)转换为键函数,以便与 Python 的排序函数(如 sorted() 和 list.sort())一起使用。Python 3 中不再支持直接使用比较函数进行排序,cmp_to_key 提供了一种兼容的方式来继续使用基于比较的排序逻辑。

在 Python 2 中,排序函数可以接受一个 cmp 参数,用于指定一个比较函数。然而,在 Python 3 中,这个参数被移除,排序函数改为接受 key 参数。key 参数需要一个函数,该函数从每个待比较元素中提取一个用于比较的值。

cmp_to_key 的主要作用是将一个旧式的比较函数转换为一个可以用于 key 参数的函数。它返回一个 KeyWrapper 对象,该对象实现了所有必要的比较方法(如 __lt__, __gt__, __eq__ 等),从而使得排序函数能够正常工作。

使用方法

以下是如何使用 cmp_to_key 的示例:

from functools import cmp_to_key

# 定义一个旧式比较函数
def compare(x, y):
    if x < y:
        return -1
    elif x > y:
        return 1
    else:
        return 0

# 使用cmp_to_key将比较函数转换为键函数
sorted_list = sorted([5, 2, 4, 1, 3], key=cmp_to_key(compare))
print(sorted_list)  # 输出: [1, 2, 3, 4, 5]

特性和注意事项

  • 兼容性:cmp_to_key 提供了一种在 Python 3 中继续使用旧式比较函数的方法,使得从 Python 2 迁移的代码更容易适应新的环境。
  • 性能:虽然 cmp_to_key 使得旧式比较函数能够在 Python 3 中工作,但使用键函数通常更高效,建议在可能的情况下重写为键函数。
  • 实现细节:cmp_to_key 返回的对象实现了必要的比较运算符,以便排序算法能够正常比较元素。
  • 用途限制:cmp_to_key 主要用于与排序相关的场景,对于其他需要比较的场合,建议直接实现键函数或使用 Python 3 的新特性。

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注