functools模块

functools.partial(func, /, *args, **keywords): 有的翻译为偏函数,本质上是装饰器和闭包。 给目标函数传入指定的实参后,返回一个新的函数(其实是Partial对象,但仍然是callable),新函数会"冻结"一部分参数(用位置传参的参数,在调用新函数时无法再改变其实参,但用关键字传参的参数,在调用新函数时仍然可以通过关键字传参来改变实参)。
该函数的大致的等价实现如下:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

下面示例演示了我们在调用partial函数时,用位置传参与用关键字传参,会导致生成的Partial对象有不同的签名。

import inspect
from functools import partial

def add(x,y):
    print(x,y)
    return x + y


new_fun = partial(add, y=5)
print(new_fun(3))
print(inspect.signature(new_fun))

print('*' * 50)
new_fun2 = partial(add, x=3)
print(new_fun2(y=4))
print(inspect.signature(new_fun2))

print('*' * 50)
new_fun3 = partial(add, 3)
print(new_fun3(4))
print(inspect.signature(new_fun3))

输出结果:

3 5
8
(x, *, y=5)
**************************************************
3 4
7
(*, x=3, y)
**************************************************
3 4
7
(y)

Patial对象可以当做函数再次传给partial函数。

import inspect
from functools import partial

def add(x,y,z):
    print(x, y, z)


print(inspect.signature(add))

print('*' * 50)
new_fun = partial(add, 3)
print(inspect.signature(new_fun))

print('*' * 50)
new_fun2 = partial(new_fun, 4)
print(inspect.signature(new_fun2))

输出结果:

(x, y, z)
**************************************************
(y, z)
**************************************************
(z)

下面演示,当partial函数使用关键字传参来生成Partial对象时,Partial签名里相当于有了关键字参数及缺省值,因此我们可以通过对Partial对象进行关键字传参,用实参来替代缺省值。

def add(x,y,z):
    print(x, y, z)


print(inspect.signature(add))

print('*' * 50)
new_fun = partial(add, x=3)
print(inspect.signature(new_fun))

print('*' * 50)
new_fun2 = partial(new_fun, y=4)
print(inspect.signature(new_fun2))

print('*' * 50)
new_fun2(y=5,z=6)  #通过关键字传参,来修改Partial对象签名中的关键字参数

输出结果:

(x, y, z)
**************************************************
(*, x=3, y, z)
**************************************************
(*, x=3, y=4, z)
**************************************************
3 5 6

functools.partialmethod(func, /, *args, **keywords): 功能及用法都与partial函数类似,只不过它应该用在类内部,作用于类中的方法上。

from functools import partialmethod

class MyClass:
    def __init__(self, value):
        self.value = value

    def multiply(self, x, y):
        print(x, y)
        return self.value * x * y

    # 使用 partialmethod 创建一个新的方法,预先绑定 y=2
    multiply_by_2 = partialmethod(multiply, y=2)

# 创建实例
obj = MyClass(10)

# 调用原方法
print(obj.multiply(3, 4))  # 输出: 120 (10 * 3 * 4)

# 调用部分应用的方法
print(obj.multiply_by_2(3))  # 输出: 60 (10 * 3 * 2)

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): 此函数解决了装饰器的一个问题,即装饰一个函数后,返回的新函数的__doc____name__等属性被替换成了wrapper的属性,而非原函数的属性。
下面示例展示普通装饰器的效果:

def decorator(fn):
    def wrapper(*args, **kwargs):
        '''这是wrapper函数'''
        print('打印参数', args, kwargs)
        return fn(*args, **kwargs)
    return wrapper

@decorator
def add(x, y):
    '''这是add函数'''
    return x + y

print(add(3,4))
print(add.__name__)
print(add.__qualname__)
print(add.__doc__)

输出结果:

打印参数 (3, 4) {}
7
wrapper
decorator.<locals>.wrapper
这是wrapper函数

现在我们看下update_wrapper函数的使用。

from functools import update_wrapper

def decorator(fn):
    def wrapper(*args, **kwargs):
        '''这是wrapper函数'''
        print('打印参数', args, kwargs)
        return fn(*args, **kwargs)

    update_wrapper(wrapper, wrapped=fn)
    return wrapper

@decorator
def add(x, y):
    '''这是add函数'''
    return x + y

print(add(3,4))
print(add.__name__)
print(add.__qualname__)
print(add.__doc__)

输出结果:

打印参数 (3, 4) {}
7
add
add
这是add函数

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): 此函数是update_wrapper的装饰器用法,功能一样。
下面示例用wraps装饰器替换上面的update_wrapper方法。注意,不论是使用装饰器,还是update_wrapper,返回的新函数都多了一个__wrapped__属性,指向原函数。

from functools import wraps

def decorator(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        '''这是wrapper函数'''
        print('打印参数', args, kwargs)
        return fn(*args, **kwargs)
    return wrapper

@decorator
def add(x, y):
    '''这是add函数'''
    return x + y

print(add(3,4))
print(add.__name__)
print(add.__qualname__)
print(add.__doc__)
print(add.__wrapped__)

输出结果:

打印参数 (3, 4) {}
7
add
add
这是add函数
<function add at 0x0000014D1841A5C0>

@functools.singledispatch: 单派发泛型函数装饰器。 就是为python实现对函数的重载。 以第一个形参的类型做为判断条件,不同类型为函数重载。
对一个函数使用此装饰器后,以被装饰的函数.register为装饰器去装饰另一个函数。 如果被装饰的函数的第一个形参有类型提示,则register不需要传参数,否则,需要传递类型参数。
下面示例因为被装饰的函数fun_str(x, y)第一个参数没有类型提示,而register也没传递类型参数,所以会报错。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register
def fun_str(x, y):
    print("参数是str", type(x), x, type(y), y)

输出结果:

Traceback (most recent call last):
  File "C:\WorkFolder\PythonProjects\pythonStudy\fortest.py", line 7, in <module>
    @fun.register
     ^^^^^^^^^^^^
  File "C:\WorkFolder\FreeInstallSoftware\Python\Python311\Lib\functools.py", line 870, in register
    raise TypeError(
TypeError: Invalid first argument to `register()`: <function fun_str at 0x00000274ED1E0B80>. Use either `@register(some_class)` or plain `@register` on an annotated function.

对其修改, 将被装饰的参数的第一个形参添加类型提示后,运行通过。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register
def fun_str(x: str, y):
    print("参数是str", type(x), x, type(y), y)


fun(123, 456)
fun('123', 456)

输出结果:

参数是object <class 'int'> 123 <class 'int'> 456
参数是str <class 'str'> 123 <class 'int'> 456

下面是另一种方式,不加类型提示,但是register传递类型参数。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register(str)
def fun_str(x, y):
    print("参数是str", type(x), x, type(y), y)


fun(123, 456)
fun('123', 456)

输出结果:

参数是object <class 'int'> 123 <class 'int'> 456
参数是str <class 'str'> 123 <class 'int'> 456

可以看到,输出效果相同。
由于添加重载函数时,函数名已经不重要了,所以可以用_来代替。 另外,当调用函数时的实参类型无法匹配到用register函数装饰的重载函数时,就会调用默认的函数,即使用@singledispatch装饰的函数, 上面的示例中实参传递是整数的情况就体现了这一点。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register(str)
def fun_str(x, y):
    print("参数是str", type(x), x, type(y), y)

@fun.register
def _(x: int, y):
    print("参数是int", type(x), x, type(y), y)

fun(123, 456)
fun('123', 456)

输出结果:

参数是int <class 'int'> 123 <class 'int'> 456
参数是str <class 'str'> 123 <class 'int'> 456

形参的类型提示可以合并多个类型。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register
def _(x: float|None, y):
    print("参数是float或None", type(x), x, type(y), y)

fun(123.0, 456)
fun(None, 456)

输出结果:

参数是float或None <class 'float'> 123.0 <class 'int'> 456
参数是float或None <class 'NoneType'> None <class 'int'> 456

我们还可以用被@singledispatch装饰的函数名.dispatch,然后传递一个类型,返回一个对应的函数对象。

from functools import singledispatch

@singledispatch
def fun(x, y):
    print("参数是object", type(x), x, type(y), y)

@fun.register
def _(x: int, y):
    print("参数是int", type(x), x, type(y), y)

@fun.register
def _(x: float|None, y):
    print("参数是float或None", type(x), x, type(y), y)


print(fun.dispatch(int))   # <function _ at 0x00000209970A0B80>
print(fun.dispatch(float))   # <function _ at 0x00000209970A0C20>
print(fun.dispatch(type(None)))  # <function _ at 0x00000209970A0C20>

fun.dispatch(type(None))(999, 888)  # 参数是float或None <class 'int'> 999 <class 'int'> 888

输出结果:

<function _ at 0x00000191A2BD0B80>
<function _ at 0x00000191A2BD0C20>
<function _ at 0x00000191A2BD0C20>
参数是float或None <class 'int'> 999 <class 'int'> 888

functools.singledispatchmethod(func): 用法与functools.singledispatch一样,只不过它是用来重载类定义中的方法。

from functools import singledispatchmethod

class Foo:
    @singledispatchmethod
    def fun(self, x, y):
        print("参数是object", type(x), x, type(y), y)

    @fun.register
    def _(self, x: int, y):
        print("参数是int", type(x), x, type(y), y)

    @fun.register
    def _(self, x: float | None, y):
        print("参数是float或None", type(x), x, type(y), y)


foo = Foo()
foo.fun(123, 456)
foo.fun(None, 456)

输出结果:

参数是int <class 'int'> 123 <class 'int'> 456
参数是float或None <class 'NoneType'> None <class 'int'> 456

@functools.lru_cache: 缓存函数返回值的装饰器。支持多线程使用。 有两种方式,一种是不带参数的,一种是带参数的。 不带参数的等价于传给参数缺省值 @functools.lru_cache(maxsize=128, typed=False)
被装饰的函数,调用时,其实参及对应的返回值被缓存起来。 缓存(LRU), least recently used, 最近最少使用缓存,即最近一段时间最少被使用的数据会被新数据挤出缓存。 函数调用时传递参数方式不同,不会当成相同的key来缓存。 缓存的key就是函数的实参,值就是函数返回值。 命中缓存时,函数直接返回值,而不会执行函数内部其它代码。
maxsize是缓存的数量,即key的数量。 typed=False,代表不同参数类型视为不同的key,比如foo(x=3)和foo(x=3.0)。
可以调用cache_info()函数来查看缓存信息, 调用cache_clear()函数清空缓存。最后还有一个cache_parameters()函数用来查看当时装饰时传递的参数,这个功能看起来没啥用。
由于缓存是以mapping表结构存储的,key是实参,所以实参必须是可hash的。 比如,实参如果是列表,就无法缓存。

from functools import lru_cache
import time

@lru_cache(maxsize=3)  # maxsize 指定缓存的最大条目数
def my_function(x):
    print(f"Computing for {x}")
    time.sleep(1)
    return x * x

print(my_function(2))  # 计算并缓存
print(my_function(3))   # 计算并缓存
print(my_function(4))   # 计算并缓存

print('-' * 25, '分隔线', '-' * 25)
print(my_function(5))  # 计算并缓存

print('-' * 25, '分隔线', '-' * 25)
print(my_function(3))   # 直接从缓存中取出
print(my_function(4))   # 直接从缓存中取出
print(my_function(2))   # 2被移出缓存了,需重新计算

print('-' * 25, '分隔线', '-' * 25)
print(my_function.cache_info())

print('-' * 25, '分隔线', '-' * 25)
my_function.cache_clear()  # 清空缓存,之后会重新统计缓存命中率
print(my_function.cache_info())
print(my_function(2))
print(my_function.cache_info())

print('-' * 25, '分隔线', '-' * 25)
print(my_function(2))  # 命中缓存
print(my_function.cache_info())

print('-' * 25, '分隔线', '-' * 25)
print(my_function(x=2))  # 调用函数时传参方式不一样不能命中缓存。
print(my_function.cache_info())

print('-' * 25, '分隔线', '-' * 25)
print(my_function.cache_parameters())  # 此函数用于查看装饰时使用的参数

输出结果:

Computing for 2
4
Computing for 3
9
Computing for 4
16
------------------------- 分隔线 -------------------------
Computing for 5
25
------------------------- 分隔线 -------------------------
9
16
Computing for 2
4
------------------------- 分隔线 -------------------------
CacheInfo(hits=2, misses=5, maxsize=3, currsize=3)
------------------------- 分隔线 -------------------------
CacheInfo(hits=0, misses=0, maxsize=3, currsize=0)
Computing for 2
4
CacheInfo(hits=0, misses=1, maxsize=3, currsize=1)
------------------------- 分隔线 -------------------------
4
CacheInfo(hits=1, misses=1, maxsize=3, currsize=1)
------------------------- 分隔线 -------------------------
Computing for 2
4
CacheInfo(hits=1, misses=2, maxsize=3, currsize=2)
------------------------- 分隔线 -------------------------
{'maxsize': 3, 'typed': False}

@functools.cache(user_function): 功能与lru_catche有类似,不需要传参数,不限定缓存大小,因为不需要清除旧值,所以比带有大小限制的 lru_cache() 更小更快。支持多线程使用。

@functools.cached_property(func): 类似于@property装饰器,但增加了缓存功能。 将类中的方法转变成属性,并缓存其值。 而且后续还可以直接修改属性的值。生命周期同实例对象相同。
在方法(即被装饰后的属性)第一次调用时计算其值,并进行缓存。 下面示例中通过vars(obj)方法展示了实例的__dict__的前后变化。

from functools import cached_property
import time

class MyClass:
    @cached_property
    def expensive_computation(self):
        print("Calculating...")
        time.sleep(1)
        return 42 * 42  # 假设这里是一个计算量很大的操作

obj = MyClass()
print(vars(obj))

print('-' * 25, '分隔线', '-' * 25)
print(obj.expensive_computation)  # 第一次访问,计算并缓存
print(obj.expensive_computation)  # 第二次访问,直接返回缓存结果
print(vars(obj))

print('-' * 25, '分隔线', '-' * 25)
obj.expensive_computation = 100  # 缓存的属性可以直接修改其值
print(obj.expensive_computation)  # 第一次访问,计算并缓存
print(vars(obj))

输出结果:

{}
------------------------- 分隔线 -------------------------
Calculating...
1764
1764
{'expensive_computation': 1764}
------------------------- 分隔线 -------------------------
100
{'expensive_computation': 100}

@functools.total_ordering: 该装饰器用来装饰类,帮助我们自动补全比较方法。 我们只提供__eq__和另一个方法(比如__lt__),该装饰器就会帮我们自动推导出其他比较方法的实现(例如!=,<=,>,>=)。
但该装饰器补充的比较方法的性能稍慢,如果这会影响程序的性能,通常我们还是要手动补全其他比较方法的实现。

from functools import total_ordering

@total_ordering
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # 定义相等操作符
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    
    # 定义小于操作符
    def __lt__(self, other):
        return (self.x, self.y) < (other.x, other.y)

# 使用 Point 类
p1 = Point(1, 2)
p2 = Point(2, 3)
p3 = Point(1, 2)

print(p1 < p2)  # True
print(p1 == p3)  # True
print(p2 > p1)  # True
print(p2 >= p1)  # True
posted @ 2025-02-14 21:02  RolandHe  阅读(122)  评论(0)    收藏  举报