ITPub博客

首页 > 数据库 > 数据库开发技术 > Python高级编程:Functools模块的8个高级用法,强烈建议添加到你的开发工具箱中!

Python高级编程:Functools模块的8个高级用法,强烈建议添加到你的开发工具箱中!

原创 数据库开发技术 作者:jackzhang 时间:2024-05-22 06:30:00 0 删除 编辑

Python 是一门多功能语言,其众多优点之一就是它有很多功能强大的内置模块,这些模块使我们程序员在很多时候不用重复造轮子。functools 模块就是这样一个典型的例子。充分利用它可以让我们的 Python 代码更整洁、清晰和专业。

本文将介绍 Python 这个杰出模块的 8 个必知用途。阅读后,相信你能够在“Python 大师”的路上更进一步。

1. functools.partial

创建一个函数的新版本,并固定部分参数,减少重复传递参数的次数。类似于方法重写。

Python 有很多易用的内置函数,但是有时候对于特殊的案例我们必须向它们添加特定的参数。比如,int() 函数可以用于将字符串转换为整数。如果待转换的字符串是一个二进制整数的话,我们必须添加第二个参数:

print(f"The binary integer 101101 is equal: {int('101101', base=2)}")
# The binary integer 101101 is equal: 45

如果我们需要频繁地使用这个二进制转换功能,却总是重复写入相同的第二个参数,那将是很烦人的(同时看起来也是蠢蠢的)。

那么有没有一种方法可以让我们一处实现多处使用呢?functools 中的 partial 方法刚好可以帮助我们达到这个目的,让我们的代码变得更简洁:

from functools import partial

binary_trans = partial(int, base=2)
print(f"101101 (base 2) is equal to: {binary_trans('101101')} (base 10)")
print(f"1111110 (base 2) is equal to: {binary_trans('1111110')} (base 10)")
print(f"11111111 (base 2) is equal to: {binary_trans('11111111')} (base 10)")

这样一来,不管后续调用多少次,我们都不必给出第二个参数,实现定制化转换功能。当然,对于自定义函数,也同样可以使用 partial 方法。

from functools import partial

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

double = partial(multiply, 2)
print(double(5))  # 输出 10

这里的 partial 实现了某个数值 2 倍的定制化计算功能。

2. functools.lru_cache

使用最近最少使用(Least Recently Used, LRU)缓存装饰器,提高函数的性能,尤其适用于计算密集型或 I/O 密集型函数。

下面看一个常见的例子,即斐波那契数列数字的计算,我们分别用常规方法和缓存方法实现,看看它们之间的性能差距:

from functools import lru_cache
import timeit

def fibonacci_norm(n):
    if n < 2:
        return n
    return fibonacci_norm(n - 1) + fibonacci_norm(n - 2)

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

def timed_fibonacci_norm():
    resutl = fibonacci_norm(30)
    return resutl

def timed_fibonacci_cached():
    resutl = fibonacci_lru_cached(30)
    # 清除缓存结果
    fibonacci_lru_cached.cache_clear()
    return resutl

if __name__ == '__main__':
    time_norm = timeit.timeit(timed_fibonacci_norm, number=50)
    time_cached = timeit.timeit(timed_fibonacci_cached, number=50)
    print(f'Average time for fibonacci (norm): {time_norm / 50} s')
    print(f'Average time for fibonacci (cached): {time_cached / 50} s')
    print(f'Cached version is faster than norm: {time_norm / time_cached / 50}')

从上面的结果可以看出,相较于常规方法,使用 lru_cache 方法速度快了大约 363 倍,对性能的提升非常显著。而且随着计算数字的增大,这个差距还会继续扩大。

此外,缓存方法还有另一种实现方式,即 cache 装饰器。其实现方式与 lru_cache 类似。我们可以看看它与常规方法的性能差距:

from functools import cache
import timeit

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

def timed_fibonacci_cached():
    resutl = fibonacci_lru_cached(30)
    # 重新装饰函数以清楚缓存
    fibonacci_cached = cache(fibonacci_lru_cached)
    return resutl
    
if __name__ == '__main__':
    time_norm = timeit.timeit(timed_fibonacci_norm, number=50)
    time_cached = timeit.timeit(timed_fibonacci_cached, number=50)
    print(f'Average time for fibonacci (norm): {time_norm / 50} s')
    print(f'Average time for fibonacci (cached): {time_cached / 50} s')
    print(f'Cached version is faster than norm: {time_norm / time_cached / 50}')

可以看出,cache 的性能更好,比常规方法快了约 1213 倍,主要是因为 lru_cache 存在缓存策略,所以在缓存管理上会有一定开销。

lru_cache 和 cache 各有其优点和适用场景,性能上的差异主要取决于具体的使用场景和缓存策略的需求。下面是一些对比和选择建议:

lru_cache 的特点

  • LRU 缓存策略:lru_cache 使用最近最少使用(Least Recently Used, LRU)策略,当缓存达到最大容量时,会自动清除最久未使用的条目。这对于需要限制缓存大小并且期望自动管理缓存淘汰的场景非常有用。
  • 缓存统计信息:lru_cache 提供缓存命中率等统计信息(通过 cache_info 方法),有助于监控和优化缓存使用。
  • 性能:在缓存大小有限且需要频繁访问缓存条目的情况下,lru_cache 能够显著提升性能,但在缓存管理上会有一定的开销。

cache 的特点

  • 无缓存策略:cache 是一个简单的无策略缓存,它没有缓存淘汰机制,即缓存条目会一直保留,直到程序终止或显式清除。这适合于需要缓存所有结果且不需要考虑内存限制的场景。
  • 性能:cache 的性能开销较小,因为它没有管理缓存条目的开销,在没有内存限制和缓存淘汰需求的情况下,cache 可以提供更好的性能。

性能比较与选择

  • 内存限制:如果你的应用需要限制内存使用,并且缓存数据量可能很大,选择 lru_cache 更为合适,因为它能够自动管理缓存大小并清除旧条目。
  • 缓存条目数量少:如果缓存的数据量较少,且不会超出内存限制,使用 cache 会更加简单高效,因为它没有缓存管理的开销。
  • 缓存管理需求:如果你需要了解缓存的使用情况和命中率,lru_cache 提供的统计信息会很有帮助。
  • 性能测试:对于特定应用场景,可以通过实际测试来比较两者的性能。可以使用 timeit 模块进行多次调用的时间测量,评估两者在实际应用中的表现。

3. functools.reduce

functools.reduce 是 Python 很重要的高阶函数之一,它用于对可迭代对象中的元素进行累计操作,最终将其简化为单一的值。reduce 可以说是一个“归约”函数,通过对序列中的元素依次应用指定的二元操作,将序列归约为一个值。

functools.reduce 的作用

reduce 的作用是对序列进行二元操作,并将序列简化为一个单一的值。它的使用格式如下:

from functools import reduce

result = reduce(function, iterable[, initializer])
  • function:一个接受两个参数的函数,reduce 会将其应用于 iterable 的元素。
  • iterable:一个可迭代对象(如列表、元组等)。
  • initializer(可选):初始值,如果提供,则首先将其与序列的第一个元素一起传递给 function。

工作原理

reduce 从 iterable 中取前两个元素,将其传递给 function,得到的结果与第三个元素一起再次传递给 function,如此重复,直到序列处理完毕,最终得到一个单一的值。

示例

累加序列中的所有元素

from functools import reduce

numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers)
print(result)  # 输出 15

在这个例子中,reduce 使用了一个匿名函数 lambda x, y: x + y,该函数接受两个参数并返回它们的和。reduce 会依次将序列中的元素累加,最终得到 15。

计算阶乘

from functools import reduce

def factorial(n):
    return reduce(lambda x, y: x * y, range(1, n + 1))

print(factorial(5))  # 输出 120

在这个例子中,reduce 用于计算阶乘,即将 1 到 n 的所有数相乘。

initializer 的使用

如果提供了 initializer,则首先将其与 iterable 的第一个元素一起传递给 function。

from functools import reduce

numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers, 10)
print(result)  # 输出 25

在这个例子中,初始值 10 首先与序列的第一个元素 1 相加,然后继续与后续元素相加,最终结果是 25。

应用场景

  • 累计计算:例如求和、求积、最大值、最小值等。
  • 序列转换:例如将二进制数字序列转换为整数。
  • 函数组合:将多个函数组合成一个单一的函数。
  • 数据聚合:在数据处理和分析中,用于对数据进行聚合操作。

示例:计算平均销售额

假设我们有一个包含产品销售数据的列表,每个元素是一个字典,包含产品类别和销售额。我们希望计算每个类别的平均销售额。

from functools import reduce

sales_data = [
    {'category': 'A', 'amount': 100},
    {'category': 'B', 'amount': 200},
    {'category': 'A', 'amount': 150},
    {'category': 'B', 'amount': 300},
    {'category': 'C', 'amount': 250}
]

# 定义一个函数,用于合并两个数据项
def merge_sales(aggregated, item):
    category = item['category']
    amount = item['amount']
    if category in aggregated:
        aggregated[category]['total'] += amount
        aggregated[category]['count'] += 1
    else:
        aggregated[category] = {'total': amount, 'count': 1}
    return aggregated
        
# 使用 reduce 对数据进行分组聚合
aggregated_sales = reduce(merge_sales, sales_data, {})
# 计算平均销售额
average_sales = {category: data['total'] / data['count'] for category, data in aggregated_sales.items()}
print(average_sales)
# {'A': 125.0, 'B': 250.0, 'C': 250.0}

小结

functools.reduce 是一个功能强大的工具,适用于需要对序列进行累计或归约操作的场景。它可以通过二元操作将序列中的元素逐步简化为一个单一的值,对于某些复杂的数据处理和聚合操作非常有用。然而,由于其使用较为复杂,对于某些简单的需求,sum、min、max 等内置函数可能更加直观和高效。

4. functools.update_wrapper

Python 中的装饰器可以帮助我们编写耦合度更低的代码。然而,它们不容易处理。有时候我们在正常代码中可能会遇到意想不到的问题:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('Something is happening before the function is called.')
        result = func(*args, **kwargs)
        print('Something is happening after the function is called.')
        return result
    return wrapper

@my_decorator
def say_hello():
    """This is a greet function."""
    print('Hello!')
    
say_hello()
print(say_hello.__name__)
print(say_hello.__doc__)

上面的代码片段成功定义了一个装饰器 @my_decrator 并将其应用到函数 say_hello() 上。

但是 __name__ 和 __doc__ 似乎并没有正确打印 say_hello() 的元数据信息。在生产系统中,这会导致严重的 bug,因为如果连正确的名称都无法获取,就可能会调用错误的函数。

这就是使用装饰器的副作用。你不仅需要“包装”函数,还需要包装其元数据,如名称、文档等。

functools.update_wrapper 就可以达到包装函数的同时,并且复制被包装函数的元数据信息。update_wrapper 主要用于将原始函数的属性复制到包装器函数上,包括以下属性:

  • __module__: 模块名
  • __name__: 函数名
  • __qualname__: 函数的限定名
  • __annotations__: 函数的注解
  • __doc__: 函数的文档字符串
from functools import update_wrapper

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('Something is happening before the function is called.')
        result = func(*args, **kwargs)
        print('Something is happening after the function is called.')
        return result
    update_wrapper(wrapper, func)
    return wrapper

@my_decorator
def say_hello():
    """This is a greet function."""
    print('Hello!')
    
say_hello()
print(say_hello.__name__)
print(say_hello.__doc__)

在这个例子中:

  1. 定义装饰器 my_decorator:
  • wrapper 函数包装了传入的函数 func,在调用 func 前后打印一些信息。
  • 使用 update_wrapper(wrapper, func) 将 func 的属性复制到 wrapper 上。
  1. 装饰 say_hello 函数:
  • say_hello 被 my_decorator 装饰,变成了 wrapper 函数。
  • 由于使用了 update_wrapper,say_hello 仍然保留了原始函数的名称和文档字符串等属性。所以正确打印了函数的元数据信息。

应用场景

functools.update_wrapper 在以下场景中非常有用:

  • 开发自定义装饰器:当你开发自定义装饰器时,如果不使用 update_wrapper 或 functools.wraps,包装器函数将丢失原始函数的重要元数据。这样可能会影响调试、文档生成等工具的使用。

  • 保持代码可读性:保持装饰器包装后的函数名称和文档字符串不变,有助于代码的可读性和维护性。即使函数被装饰后,开发者仍然可以通过查看函数的名称和文档字符串了解其功能。

  • 调试和日志记录:在调试和日志记录中,保持函数的元数据可以提供准确的函数调用信息,有助于定位问题。

5. functools.wraps

functools.wraps 是基于 update_wrapper 实现的一个装饰器,它可以实现与 update_wrapper 相同的功能,只是可以简化装饰器函数的编写。

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper

@my_decorator
def say_hello():
    """This is a greeting function."""
    print("Hello!")

say_hello()
print(say_hello.__name__)  # 输出 say_hello
print(say_hello.__doc__)   # 输出 This is a greeting function.

那么可能有人可能会问,既然有了更便捷的 wraps,为什么还需要 update_wrapper 呢?

虽然 functools.wraps 提供了更便捷的方法来更新包装器函数的元数据,但 functools.update_wrapper 仍然是一个重要且有用的工具,原因如下:

灵活性和自定义控制

  • 自定义装饰器逻辑:update_wrapper 提供了更灵活的使用方式,使得开发者可以在装饰器中进行更多自定义操作。例如,如果你需要在装饰器中进行一些额外的初始化或处理,可以使用 update_wrapper 来实现更复杂的逻辑。

    from functools import update_wrapper

    def my_decorator(f):
        def wrapper(*args, **kwargs):
            print("Before function call")
            result = f(*args, **kwargs)
            print("After function call")
            return result
        update_wrapper(wrapper, f)
        return wrapper
  • 选择性复制属性:有时候,你可能不需要复制所有的元数据属性。update_wrapper 允许你选择性地复制某些属性,而忽略其他属性。wraps 是基于 update_wrapper 实现的,但它复制了所有默认的元数据属性。

    from functools import update_wrapper

    def my_decorator(f):
        def wrapper(*args, **kwargs):
            print("Before function call")
            result = f(*args, **kwargs)
            print("After function call")
            return result
        # 仅复制部分属性
        update_wrapper(wrapper, f, assigned=('__name__', '__doc__'))
        return wrapper

低级控制和组合

  • 与其他装饰器组合使用:在某些情况下,你可能需要将 update_wrapper 与其他装饰器或工具结合使用,以实现更复杂的装饰器逻辑。wraps 虽然简洁,但在组合多个装饰器时,update_wrapper 可以提供更低级的控制。

    from functools import update_wrapper, lru_cache

    def my_decorator(f):
        @lru_cache(maxsize=32)
        def wrapper(*args, **kwargs):
            print("Before function call")
            result = f(*args, **kwargs)
            print("After function call")
            return result
        update_wrapper(wrapper, f)
        return wrapper

理解装饰器实现细节

  • 学习和理解:对于那些想深入理解装饰器工作原理的人来说,了解 update_wrapper 的使用方式和内部机制是非常重要的。它可以帮助开发者更好地理解装饰器的底层实现和元数据处理。

小结

虽然 functools.wraps 更加简洁和易用,但 functools.update_wrapper 提供了更灵活和低级的控制,对于需要更高自定义控制、选择性复制属性或与其他装饰器组合使用的场景来说,update_wrapper 是必不可少的工具。理解并掌握 update_wrapper 的使用,可以让你在编写复杂装饰器时游刃有余。

6. functools.total_ordering

functools.total_ordering 用于简化实现自定义类的全部比较操作。通常,如果你想让一个类支持所有的比较操作(如 <、<=、>、>=),需要实现这些操作的所有方法。而 total_ordering 则可以用于减少代码量,你只需实现部分方法(__eq__ 和一个其他的比较方法),其余的比较方法 total_ordering 会自动生成。

functools.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

# 现在 Person 类自动支持所有比较操作
alice = Person("Alice", 30)
bob = Person("Bob", 25)

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

在这个例子中,我们只实现了 __eq__ 和 __lt__ 方法,但 total_ordering 装饰器自动为 Person 类生成了 __le__、__gt__ 和 __ge__ 方法。

原理与实现

当你使用 @total_ordering 装饰一个类时,装饰器会检查该类是否至少定义了以下方法中的两个:__lt__、__le__、__gt__、__ge__、__eq__。如果是,则基于这些方法自动生成其余的比较方法。

具体来说,total_ordering 按如下规则生成缺失的方法:

  • 如果定义了 __lt__ 和 __eq__:

    • __le__ 被定义为 lambda self, other: self.__lt__(other) or self.__eq__(other)
    • __gt__ 被定义为 lambda self, other: not self.__lt__(other) and not self.__eq__(other)
    • __ge__ 被定义为 lambda self, other: not self.__lt__(other)
  • 如果定义了 __le__ 和 __eq__:

    • __lt__ 被定义为 lambda self, other: self.__le__(other) and not self.__eq__(other)
    • __gt__ 被定义为 lambda self, other: not self.__le__(other)
    • __ge__ 被定义为 lambda self, other: not self.__le__(other) or self.__eq__(other)
  • 如果定义了 __gt__ 和 __eq__:

    • __ge__ 被定义为 lambda self, other: self.__gt__(other) or self.__eq__(other)
    • __lt__ 被定义为 lambda self, other: not self.__gt__(other) and not self.__eq__(other)
    • __le__ 被定义为 lambda self, other: not self.__gt__(other)
  • 如果定义了 __ge__ 和 __eq__:

    • __gt__ 被定义为 lambda self, other: self.__ge__(other) and not self.__eq__(other)
    • __lt__ 被定义为 lambda self, other: not self.__ge__(other)
    • __le__ 被定义为 lambda self, other: not self.__ge__(other) or self.__eq__(other)

应用场景

total_ordering 主要用于以下场景:

  • 简化代码实现:当你需要为自定义类实现所有比较操作时,只需实现最少的方法,剩余的方法由 total_ordering 自动生成,减少了重复代码。

  • 提高代码可维护性:通过减少重复实现比较方法,可以使代码更简洁、更易于维护。

  • 确保一致性:通过自动生成的方法,确保了比较操作的一致性,避免手动实现时可能出现的错误。

小结

functools.total_ordering 是一个非常实用的装饰器,用于简化和标准化类的比较操作实现。它通过自动生成缺失的比较方法,使得开发者只需实现最少的方法即可确保类支持全部的比较操作,从而提高代码的可读性和可维护性。在需要实现多个比较操作的类中,使用 total_ordering 可以显著减少代码量和潜在的错误。

7. functools.singledispatch

functools.singledispatch 是 Python 标准库 functools 模块中的一个装饰器,用于实现单分派泛型函数。单分派泛型函数根据第一个参数的类型进行分派,不同类型的参数会调用不同的函数实现。这在需要根据输入参数的类型执行不同逻辑的场景中非常有用。

functools.singledispatch 的作用

singledispatch 使得你可以定义一个通用函数,并为不同类型的第一个参数注册不同的实现。它是 Python 中对函数进行多态操作的一种方式,类似于其他编程语言中的方法重载(method overloading)。

下面是一个简单的示例,展示如何使用 singledispatch:

from functools import singledispatch

@singledispatch
def process(data):
    raise NotImplementedError('Cannot process this type')

@process.register(int)
def _(data):
    return f'Processing integer: {data}'
    
@process.register(str)
def _(data):
    return f'Processing string: {data}'
    
@process.register(list)
def _(data):
    return f'Processing list: {data}'
    
print(process(10))                          # Processing integer: 10
print(process('Jack Zhang'))                # Processing string: Jack Zhang
print(process(['Jack Zhang', 111, 888]))    # Processing list: ['Jack Zhang', 111, 888]
print(process({'name': 'Jack Zhang'}))      # NotImplementedError: Cannot process this type

在这个例子中:

  • 定义通用函数 process:使用 @singledispatch 装饰器将 process 定义为一个单分派泛型函数。这个函数在没有匹配类型的实现时,会抛出 NotImplementedError。

  • 注册具体类型的实现:使用 @process.register(type) 为不同的类型注册具体的实现。例如,int 类型、str 类型和 list 类型。

原理与实现

singledispatch 基于被装饰的通用函数,创建一个调度机制,该机制会根据第一个参数的类型调用相应的注册实现。具体来说:

  • @singledispatch 装饰器将一个函数标记为通用函数。
  • 使用 @通用函数.register(type) 语法,为特定类型注册具体的实现。
  • 调用通用函数时,根据第一个参数的类型,调度器会找到并调用相应的实现。

应用场景

functools.singledispatch 在以下场景中非常有用:

  • 数据处理:处理不同类型的数据,例如字符串、数字、列表、字典等,可以根据数据类型调用相应的处理逻辑。

  • API 设计:在设计 API 时,根据传入参数的类型调用不同的处理函数,使 API 更加灵活和可扩展。

  • 类型特定的操作:在科学计算、数据分析等领域,根据输入数据类型执行特定操作,例如矩阵运算、数据转换等。

使用 singledispatchmethod 装饰类方法

Python 3.8 引入了 singledispatchmethod,它允许你在类中使用单分派泛型方法。下面是一个示例:

from functools import singledispatchmethod

class DataHandler:
    @singledispatchmethod
    def handle(self, data):
        raise NotImplementedError(f'Cannot handle type {type(data)}')
    
    @handle.register(int)
    def _(self, data):
        return f'Handling integer: {data}'
    
    @handle.register(str)
    def _(self, data):
        return f'Handling string: {data}'
    
handler = DataHandler()
print(handler.handle(10))
print(handler.handle('Jack Zhang'))

try:
    print(handler.handle(['Jack Zhang', 888]))
except NotImplementedError as e:
    print(e)

在这个示例中,DataHandler 类使用 @singledispatchmethod 装饰器定义了一个泛型方法 handle,并为 int 和 str 类型注册了具体的实现。

小结

functools.singledispatch 是一个强大的工具,用于根据第一个参数的类型选择不同的函数实现,简化了多态函数的编写。它适用于需要根据输入参数类型执行不同逻辑的场景,提高了代码的可读性和维护性。在 Python 3.8 及以上版本中,可以使用 singledispatchmethod 装饰类方法,实现类似的多态行为。

8. functools.cached_property (Python 3.8+)

functools.cached_property 是 Python 3.8 引入的一个装饰器,用于将类方法转换为属性,并缓存其结果。与普通属性不同,cached_property 只在第一次访问时计算一次,并将结果缓存起来,后续访问将直接返回缓存的结果,而不会重新计算。这对于一些计算开销较大的属性非常有用,可以显著提高性能。

functools.cached_property 的作用

cached_property 的主要作用是将类的方法转换为只计算一次并缓存结果的属性。这可以减少重复计算,提高访问速度。

下面是一个简单的示例,展示如何使用 cached_property:

from functools import cached_property

class Data:
    def __init__(self, value):
        self.value = value
        
    @cached_property
    def expensive_computation(self):
        print('Performing expensive computation...')
        return self.value * 2
    
data = Data(10)
print(data.expensive_computation)
print(data.expensive_computation)

在这个例子中,expensive_computation 方法被 @cached_property 装饰,第一次访问时会进行计算并打印“Performing expensive computation...”,之后的访问将直接返回缓存的结果,不会再次打印。

functools.cached_property 的实现原理

cached_property 的实现基于描述器协议。它将计算结果存储在实例的 __dict__ 属性中,以便后续访问可以直接从缓存中获取结果,而无需重新计算。

详细示例

以下是一个更复杂的示例,展示如何使用 cached_property 计算和缓存一些复杂的计算结果:

from functools import cached_property
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius
        
    @cached_property
    def area(self):
        print('Calculating area...')
        return math.pi * self.radius ** 2
    
    @cached_property
    def circumference(self):
        print('Calculating circumference...')
        return 2 * math.pi * self.radius
    
circle = Circle(10)
print(circle.area)
print(circle.area)

print(circle.circumference)
print(circle.circumference)

在这个例子中,Circle 类有两个使用 @cached_property 装饰的属性 area 和 circumference。第一次访问这些属性时会进行计算并缓存结果,之后的访问将直接返回缓存的结果。

应用场景

functools.cached_property 在以下场景中非常有用:

  • 昂贵的计算:当计算某个属性的值非常耗时,并且在对象的生命周期中多次访问时,使用 cached_property 可以显著提高性能。

  • 不可变对象:对于不可变对象,属性值一旦计算出来就不会改变,因此缓存这些值非常有用。

3- 延迟计算:某些属性的计算可能依赖于其他属性,使用 cached_property 可以实现延迟计算,直到属性第一次被访问时才进行计算。

注意事项

  • 不可变性:cached_property 适用于那些在实例生命周期内不会改变的属性。如果属性可能会改变,则需要显式清除缓存或使用其他机制。

  • 线程安全:在多线程环境中,cached_property 可能需要额外的同步措施,以确保属性的计算和缓存是线程安全的。

小结

functools.cached_property 是一个非常有用的工具,用于将类的方法转换为只计算一次并缓存结果的属性。它可以显著提高属性计算开销较大的类的性能,尤其适用于不可变对象和需要延迟计算的场景。通过缓存计算结果,cached_property 可以减少重复计算,提升程序效率。

结论

在Python开发中,functools 模块不仅能简化我们的代码,还能使其更加专业和易于维护。

作为Python开发者,掌握如何高效地使用 functools 模块,可以显著提升我们的编程水平。

感谢你的阅读,有任何问题欢迎留言告诉我!


推荐阅读👇👇👇

Python编程技巧:那些年后悔没有早知道string模块的这些宝藏功能~~~

Python效率秘籍:掌握这8个内存优化技巧,让你的代码飞起来!!!

Python——函数定义中的“/”和“*”到底有什么用?一次性讲清楚~~

Python魔法符号:20秒搞懂@在装饰器中的作用原理

成为Python高手:掌握这5个核心技能,迈入编程精英行列!

- END -

🌟 喜欢这篇文章吗?给个赞支持一下吧!🌟

如果你发现今天的内容对你有帮助,不妨点个赞并关注我,不错过更多关于Python、数据分析和机器学习的深入分析和最新趋势。我们一起探索数据的力量,解锁AI的潜能。您的每一次互动都是对我的极大鼓励,也是我们共同成长的动力!

👇👇👇 关注并开启星标提醒,让知识与技能的火花不断在你的职业道路上燃烧吧!

原文作者:数据派探险家;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2017-12-19

  • 博文量
    119
  • 访问量
    55941