此函数主要用作将Python 2 程序转换至新版的转换工具,以保持对比较函数的兼容。
因为新版 python,sorted(iterable, *, key=None, reverse=False) 函数中 key 调用的函数,只能传入一个参数。 而旧版的 key 调用函数时,是需要传入两个参数的。
import functools
a = ['a', 'bb', 'c','dddd','eee']
""" =========== 旧版的排序调用函数 =============
需要传入两个参数(列表中的前后两个元素),
sorted函数通过判断这两个参数“运算”后的正负值,来确定排序。
当值为 负 时,x 就排在 y前面(即系统认为 x 比 y小),
当值为 正 时,x 就排在 y后面(即系统认为 x 比 y大)"""
def old_cmp(x, y):
return len(x) - len(y)
b = sorted(a, key=functools.cmp_to_key(old_cmp)) # 通过 cmp_to_key 转为新版
print(b) # >>> ['a', 'c', 'bb', 'eee', 'dddd']
""" =========== 新版的排序调用函数 =============
只需要传入一个参数就可以了,sorted会自动的去判断列表中前后元素通过key的返回值大小"""
def new_cmp(i):
return len(i)
n = sorted(a, key=new_cmp)
print(n) # >>> ['a', 'c', 'bb', 'eee', 'dddd']
一个为函数提供缓存功能的装饰器,缓存maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O 函数的调用时间。
由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。
如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。 maxsize 设置为2的幂时可获得最佳性能。
如果 typed 设置为true,不同类型的函数参数将被分别缓存。例如, f(3) 和 f(3.0) 将被视为不同而分别缓存。
为了衡量缓存的有效性以便调整 maxsize 形参,被装饰的函数带有一cache_info() 函数。当调用 cache_info() 函数时,返回一个具名元组,包含命中次数 hits(有效读取缓存的次数),未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize。在多线程环境中,命中数与未命中数是不完全准确的。
该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear() 。
原始的未经装饰的函数可以通过 wrapped 属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。
一般来说,LRU缓存只在当你想要重用之前计算的结果时使用。
因此,如果用它缓存具有副作用的函数,即在每次调用的函数它所传入的参数和之前没有任何关联的(一样的部分),这样缓存是没有效果的。
比较 下面 计算斐波那契数列的例子,一个用了缓存功能,一个没用缓存功能
import functools
@functools.lru_cache(maxsize=128)
def fib(n):
print(f'计算第{n}个值')
if n < 2:
return n
return fib(n-1) fib(n-2)
print(fib(5))
print(fib.cache_info()) # 上面调用fib(5)之后,再调用 cache_info() 查看信息。
用了缓存功能的运行结果是 每个参数都只调用了一次函数:
计算第5个值
计算第4个值
计算第3个值
计算第2个值
计算第1个值
计算第0个值
5
CacheInfo(hits=3, misses=6, maxsize=128, currsize=6)
下面不用缓存功能:
import functools
#@functools.lru_cache(maxsize=128)
def fib(n):
print(f'计算第{n}个值')
if n < 2:
return n
return fib(n-1) fib(n-2)
print(fib(5))
运算结果重复调用了好多次函数:
计算第5个值
计算第4个值
计算第3个值
计算第2个值
计算第1个值
计算第0个值
计算第1个值
计算第2个值
计算第1个值
计算第0个值
计算第3个值
计算第2个值
计算第1个值
计算第0个值
计算第1个值
5
该函数用于为func 函数的部分参数指定参数值,从而得到一个转换后的函数,程序以后调用转换后的函数时,就可以少传入那些己指定值的参数。
import functools
def add(x,y):
print(x y)
par_add = functools.partial(add,y=10)
par_add(5) # >>> 15
需要注意的是,如果绑定的是 x 参数,那么 par_add()就需要用关键字参数来传递了,如下:
import functools
def add(x,y):
print(x y)
par_add = functools.partial(add,x=10)
par_add(y = 5) # >>> 15
# par_add(5) # 如果是这样传递参数,那就相当于给 x 传了两个参数,会报错。
类似 3、partial(),这里 partialmethod() 是给类中的方法加参数的。
import functools
class People:
def get_name(self, name, age):
print(name, age)
set_name = functools.partialmethod(get_name, '韩梅梅',18) # 会自动传递 self 给 get_name(self, name, age) 函数
def set_age(self): # 和这个效果一样。
self.get_name('哈哈',12)
p = People()
p.get_name('小红',10)
p.set_name() # >>> 韩梅梅 18
p.set_age() # >>> 哈哈 12
将两个参数的 function 从左至右积累地应用到 iterable 的条目,以便将该可迭代对象缩减为单一的值。
类似 itertools 中的 accumulate( p [,func] ) 函数。
accumulate( ) 返回一个迭代器。
reduce( ) 返回一个值。
import functools
A = [1,2,3,4,5]
def add(x,y):
return x y
S = functools.reduce(add, A)
print(S) # >>> 15
S = functools.reduce(add, A, 100) # 定义一个初始值 100
print(S) # >>> 115
@functools.wraps() 主要就是让包装函数的 name 、doc 属性与
被包装函数保持一致。 函数的这些参数的默认值是模块级常量 WRAPPER_ASSIGNMENTS (它将被赋值给 wrapper 函数的 module, name, qualname, annotations 和 doc 即文档字符串) 以及 WRAPPER_UPDATES (它将更新 wrapper 函数的 dict 即实例字典)。
1、先看没被 @functools.wraps()装饰的函数
import functools
def decorator(fun):
def wrapper_func(*args):
res = fun(*args)
print(f'{fun.__name__} 函数的计算结果为:{res}')
return wrapper_func
@decorator
def add(x,y):
return x y
add(4,5)
print(add.__name__)
运行结果:
add 函数的计算结果为:9
wrapper_func
可以看出 print(add.name) 打印出来的函数名不是 add 而是 wrapper_func。
这是因为经过decorator()函数装饰后,add() 函数已经变成了:add = decorator(add)
而 decorator(add) 的返回值是 wrapper_func。
所以为了使 print(add.name) 能打印出原来的函数名,就需要对它用@functools.wraps(wrapped) 进行包裹,
参数 wrapped 为需要读取原函数属性的函数名。
如下:
2、被 @functools.wraps()装饰的函数
import functools
def decorator(fun):
@functools.wraps(fun) # fun 为被装饰的函数
def wrapper_func(*args):
res = fun(*args)
print(f'{fun.__name__} 函数的计算结果为:{res}')
return wrapper_func
@decorator
def add(x,y):
return x y
add(4,5)
print(add.__name__)
运行结果为:
add 函数的计算结果为:9
add
功能和 @functools.wraps() 一样,只是使用方式有点不同。
1、@functools.wraps(wrapped) 只要传入一个 被装饰 的函数就可以了。
2、functools.update_wrapper(wrapper, wrapped)需要传入装饰函数和被装饰的函数
import functools
def decorator(fun):
#@functools.wraps(fun)
def wrapper_func(*args):
res = fun(*args)
print(f'{fun.__name__} 函数的计算结果为:{res}')
return functools.update_wrapper(wrapper_func, fun) # 使用方式。
@decorator
def add(x,y):
return x y
add(4,5)
print(add.__name__)
这是一个修饰 类 的装饰器。
用于自动生成比较方法,例如 > 、<、 = ……
但有个前提是,被修饰的这个类必须有下面四个方法中的一个 :
__lt__() 、__le__()、__gt__() 、 __ge__()
另外:最好 自己定义好 __ eq __() 方法,因为如果自己不定义这个方法,那么程序就会从 父类 object 中调用,从而导致 结果有偏差。
注意: 只有自己定义了上面四个方法中的一个,和 __eq__() 方法,total_ordering才会正确的生成其他的比较方法。 |
方法说明:
__eq__() : equal 等于 =
__ne__() : not equal 不等于 !=
__lt__() : less than 小于 <
__le__() : less equal 小于等于 <=
__gt__() : great than 大于 >
__ge__() : great equal 大于等于 >=
例:有一个 People 的类,它含有 姓名 和 年龄 两个属性。实例化后,可以在每个实例之间进行比较,看哪个年龄比较大。
from functools import *
@total_ordering
class People:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'名字:{self.name },年龄:{self.age}'
# 给比较函数调用,用来判断两个实例是否可比较。
def _is_valid_operand(self, other):
if hasattr(other, "name") and hasattr(other, 'age'):
return True
else:
return False
# 因为 hasattr() 函数返回的是 bool 值,所以可以直接用下面的代码替代上面的 if 语句。
# return hasattr(other, "name") and hasattr(other, 'age')
# 如果自定义 __eq__ 函数,那么调用 == 时,程序就会从 object 中调用__eq__ 函数。
def __eq__(self, other):
print('调用了 = 判断函数。')
if not self._is_valid_operand(other): # 找不到 name 和age 属性 则不能比较。
print('根据 _is_valid_operand 判断后,两个实例无法比较。')
return NotImplemented
else:
# 根据name和age是否相等,判断是不是同一个人。(名字都转成小写比较)
if self.name.lower() == other.name.lower() and self.age == other.age:
return True
else:
return False
# 因为 比较函数返回的是 bool 值,所以可以直接用下面的代码替代上面的 if 语句。
# return self.name.lower() == other.name.lower() and self.age == other.age
def __lt__(self, other):
print('调用了 < 判断函数。')
if not self._is_valid_operand(other): # 找不到 name 和age 属性 则不能比较。
print('根据 _is_valid_operand 判断后,两个实例无法比较。')
return NotImplemented
else:
# 根据age判断年龄大小。
if self.age < other.age:
return True
else:
return False
# 打印被装饰之后的People类中的__gt__方法
print(People.__gt__)
print(People.__eq__)
print(People.__lt__)
lilei = People('李雷', 12)
hanmeimei_1 = People('韩梅梅', 16)
hanmeimei_2 = People('韩梅梅', 16)
print(lilei > hanmeimei)
print(hanmeimei_1 == hanmeimei_2)
上面 People 类只定义了一个 等于 和 小于函数,通过 @total_ordering 修饰后,自动生成了其他的比较函数。
另外: 虽然此装饰器使得创建具有良好行为的完全有序类型变得非常容易,但它 确实 是以执行速度更缓慢和派生比较方法的堆栈回溯更复杂为代价的。 如果性能基准测试表明这是特定应用的瓶颈所在,则改为实现全部六个富比较方法应该会轻松提升速度。
官网解释为:将一个方法转换为 单分派 generic function。
generic function :即 泛型函数 ,它为不同的类型实现相同操作的多个函数所组成的函数。在调用时会由调度算法来确定应该使用哪个实现。
通俗讲:
当有一个函数需要根据传入的变量的类型来判断需要输出的内容时,通常的做法是在函数内部使用大量的if/elif/else来解决问题。这样做会使代码显得笨重,难以维护,也不便于扩展。
这时使用 @functools.singledispatch 它会根据传入的 第一个参数的类型,选择对应的函数,并把相同的参数传递给对应的函数,让那个函数去处理。 |
使用方法:
import functools
""" 第一步:先对需要 参数判断 的函数用 @functools.singledispatch 进行修饰。"""
@functools.singledispatch
def test(x, y):
print(f'初始函数。')
""" 第二步:把 对应参数类型 要调用的函数注册到 test()函数中。"""
# 注册函数方法1:用装饰器注册。
@test.register(int) # 同 test.register(float, fun_int ), 见下方函数注册方法二。
def fun_int(x, y):
res = x + y
print(f'这是加法函数,值为:{res}')
# 第一个参数为 int 型,它会调用 fun_int(x, y) 函数。
test(2,3) # >>> 这是加法函数,值为:5
def aaaa(x, y):
print(f'调用了 aaaa 函数')
# 注册函数方法 2:用函数注册。
test.register(type(None), aaaa) # 注册一个参数为 None 类型的参数。
# 第一个参数为 None 型,它会调用 aaa(x, y) 函数。
test(None,3) # >>> 调用了 aaaa 函数
# 注册函数方法 3:可以叠加修饰。例如下面的,可看成:当传入的第一个参数是 浮点型 或 复数型。
@test.register(complex)
@test.register(float)
def fun_float(x, y):
print(f'调用叠加注册函数,参数类型为 {type(x)}')
test(2.2, 3) # >>> 调用叠加注册函数,参数类型为
test(2.2+1j, 3) # >>> 调用叠加注册函数,参数类型为
""" 可以用 test.dispatch(cls) 查看 test 函数第一个参数为 某种类型时的 指向函数是什么函数。"""
# 查看 test 函数第一个参数为 int 类型时的 指向函数。
f = test.dispatch(int)
print(f.__name__)
f(2,3)
# 获取test函数为int类型绑定的函数
f = test.registry[float]
print(f.__name__) # 获取函数名。
f(5,6) # 执行函数。
# 获取test函数所绑定的全部类型
print(test.registry.keys())