Python 轻松应对复杂编程 functools模块速查表



文章开篇

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 reduce

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

2.计算列表中所有元素的积
from functools import reduce

numbers = [1, 2, 3, 4, 5]
product_numbers = reduce(lambda x, y: x * y, numbers)
print(product_numbers)  # 输出 120

3.连接列表中所有的字符串
from functools import reduce

strings = ['Hello', ' ', 'world', '!']
concatenated_string = reduce(lambda x, y: x + y, strings)
print(concatenated_string)  # 输出 'Hello world!'

4.使用初始值进行累积操作
from functools import reduce

numbers = [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 partialmethod

class 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 -1
    elif x > y:
        return 1
    else:
        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 time


class ExpensiveCalculation:
    def __init__(self, data):
        self.data = data
        self._calculated_result = None
        self._calculation_time = None

    @cached_property
    def calculated_result(self):
        """假设这是一个非常耗时的计算"""
        start_time = time.time()
        print("计算开始...")
        time.sleep(5)  # 假设这是一个耗时的计算
        print("计算结束...")

        self._calculated_result = sum(self.data)  # 假设这是计算的结果
        self._calculation_time = time.time() - start_time
        print(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 n
    return 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 = radius

    def __eq__(self, other):
        if isinstance(other, Circle):
            return self.radius == other.radius
        return False

    def __lt__(self, other):
        if isinstance(other, Circle):
            return self.radius < other.radius
        return 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 = func
        self.registry = {}

    def register(self, *types):
        def wrapper(f):
            self.registry[types] = f
            return f

        return wrapper

    def __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 functools


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

def 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编程中不可或缺的利器。

  • 35
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python速查表.pdf 是一份总结了Python编程语言的主要特性和语法的速查表。这个速查表提供了Python编程初学者和有经验的开发人员在编程过程中快速查找语法和功能的便利工具。 这份速查表中包含了Python语言的各种数据类型、操作符、控制流语句、函数和模块等基础知识。它还列出了Python的标准库中常用的模块和函数,以及一些常见的第三方库和框架。 对于初学者来说,Python速查表可以帮助他们快速了解Python语法和功能。当他们需要查找某个特定功能或语法时,他们可以直接在速查表中找到相关的信息,而不需要去翻阅教程或其他参考资料。这样可以节省他们的时间和精力,并使他们更加高效地学习和使用Python语言。 对于有经验的开发人员来说,Python速查表可以作为一个快速提醒和参考工具。有时候他们可能会忘记某个特定函数的使用方法或某个语法的具体细节,这时他们可以直接在速查表中查找相关信息,而不需要去查找更详细的文档或参考资料。这样可以帮助他们更加快速地解决问题和完成任务。 总的来说,Python速查表是一个非常有用的工具,它可以帮助初学者快速入门Python编程语言,也可以帮助有经验的开发人员更加高效地使用Python进行开发工作。对于任何一个学习和使用Python的人来说,Python速查表都是一份必备的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值