functools:管理函数的工具
文章目录
- 一、修饰符
- 1、partial 对象:functools.partial(原函数, *要固定的参数)
- 2、获取对象属性
- (1) func / args / keywords 属性
- (2) update_wrapper():添加原函数的__name__ / __doc__属性
- 3、partial 适用于任何可调用对象
- 4、partialmethod():将返回 callable 用作对象的非绑定方法
- 5、修饰符 wraps():对所修饰函数应用 update_wrapper()
- 二、比较
- 1、富比较:total_ordering() 为提供部分富比较方法的类增加其余的方法
- 2、比对序:cmp_to_key() 将老式比较函数转换为返回比对键的函数
- 三、缓存
- 1、lru_cache():将函数历史调用结果放在缓存中,直接读取
- 2、cache_info() 检查缓存状态 & cache_clear() 清空缓存
- 3、maxsize 参数:控制缓存的最大容量
- 4、lru_cache() 所修饰函数的参数必须 hashable
- 四、缩减数据集
- 1、reduce():可迭代对象的累积计算,生成单个值
- 2、第三个参数:为可迭代对象提供基础值
- 五、泛型函数 generic function
- 1、singledispatch():根据第一个参数的类型自动选择要调用的函数
- 2、如果类型不完全匹配,会基于继承顺序选择函数
functools
模块提供了一些工具来调整或扩展函数和其他可调用对象。
import functools
一、修饰符
functools
模块中的partial
类,用于固定一个函数的部分参数,生成一个参数更少的新函数,新函数与原函数的参数完全相同。
1、partial 对象:functools.partial(原函数, *要固定的参数)
使用functools.partial(原函数, *要固定的参数)
可以创建partial
对象。
注意,在下列代码中,p1
在创建时已经固定了参数b=4
,在调用p1
时如果要覆盖这个预设的 b 值,必须显式使用关键字参数形式(即 b=8),不能写成位置参数 8,否则会触发TypeError
异常。
def myfunc(a, b=2):"Docstring for myfunc()."print('Current args: a={}, b={}'.format(a, b))# Current args: a=a, b=3
myfunc('a', 3)# 创建 partial 对象
p1 = functools.partial(myfunc, b=4)# 调用新函数
# Current args: a=hello, b=4
p1('hello')
# Current args: a=hello, b=8
p1('hello', b=8)
# TypeError: myfunc() got multiple values for argument 'b'
p1('hello', 8)
2、获取对象属性
(1) func / args / keywords 属性
基于原函数创建的partial
对象具备func
、args
和keywords
属性。
def myfunc(a, b=2):"Docstring for myfunc()."print('Current args: a={}, b={}'.format(a, b))# 创建 partial 对象
p1 = functools.partial(myfunc, 'dafault a', b=4)# <function myfunc at 0x105123100>
print(p1.func)
# ('dafault a',)
print(p1.args)
# {'b': 4}
print(p1.keywords)
(2) update_wrapper():添加原函数的__name__ / __doc__属性
partial
对象默认不具备__name__
属性,调用会触发AttributeError
异常。虽然具备__doc__
属性,但是得到的信息却不是原函数的 docstring。
使用functools
模块中的update_wrapper()
方法可以将原函数的属性复制或添加到partial
对象:
- 复制:
functools
模块中的WRAPPER_ASSIGNMENTS
定义了新函数的哪些属性可以被原函数直接覆盖 - 添加:
WRAPPER_UPDATES
定义了原函数的哪些属性和方法可以更新到新函数中
def myfunc(a, b=2):"Docstring for myfunc()."print('Current args: a={}, b={}'.format(a, b))# 创建 partial 对象
p1 = functools.partial(myfunc, 'dafault a', b=4)# AttributeError: 'functools.partial' object has no attribute '__name__'
print(p1.__name__)
# Create a new function with partial application of the given arguments and keywords.
print(p1.__doc__)functools.update_wrapper(p1, myfunc)
# myfunc
print(p1.__name__)
# Docstring for myfunc().
print(p1.__doc__)# ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__', '__type_params__')
print(functools.WRAPPER_ASSIGNMENTS)
# ('__dict__',)
print(functools.WRAPPER_UPDATES)
3、partial 适用于任何可调用对象
partial
适用于任何可调用对象,不限于函数。
注意下面代码的最后部分,要获取__call__
方法的__doc__
属性,需要明确访问类的__call__
方法的__doc__
属性,而非实例本身。
class MyClass:"Demonstration class for functools"def __init__(self, a=None):self.a = adef __call__(self, e, f=6):"Docstring for MyClass.__call__"print('called object with:', (e, f))o = MyClass()
# 当实例化的对象被当作函数调用时,会自动执行 __call__ 方法
# called object with: ('hello', 6)
o('hello')
# 原函数没有__name__属性:AttributeError: 'MyClass' object has no attribute '__name__'
print(o.__name__)p1 = functools.partial(o, 'default for e', 8)
functools.update_wrapper(p1, o)
# called object with: ('default for e', 8)
p1()
# Demonstration class for functools
print(p1.__doc__)# Docstring for MyClass.__call__
print(MyClass.__call__.__doc__)p2 = functools.partial(MyClass, a=123)
functools.update_wrapper(p2, MyClass)
# Docstring for MyClass.__call__
print(p2.__call__.__doc__)
4、partialmethod():将返回 callable 用作对象的非绑定方法
functools
模块中的partialmethod()
返回的 callable 可以用作对象的非绑定方法。
在下面代码中,在Myclass
实例中调用method1()
方法,会将实例本身作为第一个参数传入,这与采用常规方式定义的方法是一样的。而不传递参数调用method2()
方法会触发TypeError
异常。
def standalone(self, a=1, b=2):print('called standalone with:', (self, a, b))class MyClass:def __init__(self):self.attr = 'instance attribute'method1 = functools.partialmethod(standalone)method2 = functools.partial(standalone)o = MyClass()
# called standalone with: (<__main__.MyClass object at 0x100923620>, 1, 2)
o.method1()
# TypeError: standalone() missing 1 required positional argument: 'self'
o.method2()
5、修饰符 wraps():对所修饰函数应用 update_wrapper()
functools
模块提供了一个修饰符wraps()
,它会对所修饰的函数应用update_wrapper()
。
注意,在下列代码中,当执行decorated_myfunc()
时,实际运行的是装饰器内部的decorated
函数:
- 首先执行
decorated('decorated defaults', 1)
,执行print
语句打印输出; - 然后调用
ori_func(a, b=b)
,这里的ori_func
是被装饰的函数decorated_myfunc
; - 最后执行函数
decorated_myfunc(a, b=b)
中的内容,注意参数a、b
是从decorated
中传递出来的。
def simple_decorator(ori_func):@functools.wraps(ori_func)def decorated(a='decorated defaults', b=1):print('decorated:', (a, b))return ori_func(a, b=b)return decorateddef myfunc(a, b=2):"myfunc() is not complicated"print('myfunc:', (a, b))return# Wrap explicitly
wrapped_myfunc = simple_decorator(myfunc)# myfunc
print(wrapped_myfunc.__name__)
# myfunc() is not complicated
print(wrapped_myfunc.__doc__)# decorated: ('decorated defaults', 1)
# myfunc: ('decorated defaults', 1)
wrapped_myfunc()
# decorated: ('args to wrapped', 4)
# myfunc: ('args to wrapped', 4)
wrapped_myfunc('args to wrapped', 4)# Wrap with decorator syntax
@simple_decorator
def decorated_myfunc(a, b):myfunc(a, b)return# decorated: ('decorated defaults', 1)
# myfunc: ('decorated defaults', 1)
decorated_myfunc()
# decorated: ('args to decorated', 4)
# myfunc: ('args to decorated', 4)
decorated_myfunc('args to decorated', 4)
二、比较
Python3 提供了富比较方法 API(__lt__()
、__le__()
、__eq__()
、__ne__()
、__gt__()
、__ge__()
),结合functools
模块中的一些工具,可以更容易地编写满足比较需求的新类。
1、富比较:total_ordering() 为提供部分富比较方法的类增加其余的方法
total_ordering()
类修饰符可以为一个提供了部分富比较方法的类增加其余的方法。
这个类必须初始提供__eq__()
(或__ne__()
)和另一个富比较方法(不能再是等于或不等于关系)的实现。如果某个方法无法完成比较,该方法会返回NotImplemented
,转而再使用逆比较方法尝试比较,如果仍然无法比较,便会完全失败。
import inspect@functools.total_ordering
class MyObject:def __init__(self, val):self.val = valdef __eq__(self, other):print('testing __eq__({}, {})'.format(self.val, other.val))return self.val == other.valdef __gt__(self, other):print('testing __gt__({}, {})'.format(self.val, other.val))return self.val > other.val# inspect.getmembers 用于获取对象的所有属性和方法,并以列表形式返回,每个成员以 (名称, 值) 的元组形式存在
# [('__eq__', <function MyObject.__eq__ at 0x1055b6200>),
# ('__ge__', <function _ge_from_gt at 0x105429300>),
# ('__gt__', <function MyObject.__gt__ at 0x1055b62a0>),
# ('__init__', <function MyObject.__init__ at 0x1055a1c60>),
# ('__le__', <function _le_from_gt at 0x1054293a0>),
# ('__lt__', <function _lt_from_gt at 0x105429080>)]
print(inspect.getmembers(MyObject, inspect.isfunction))a = MyObject(1)
b = MyObject(2)for expr in ['a < b', 'a <= b', 'a == b', 'a != b', 'a >= b', 'a > b']:print('\n{:<6}:'.format(expr))result = eval(expr)print('result of {}: {}'.format(expr, result))>>> 输出结果:a < b :testing __gt__(1, 2)testing __eq__(1, 2)result of a < b: Truea <= b:testing __gt__(1, 2)result of a <= b: Truea == b:testing __eq__(1, 2)result of a == b: Falsea != b:testing __eq__(1, 2)result of a != b: Truea >= b:testing __gt__(1, 2)testing __eq__(1, 2)result of a >= b: Falsea > b :testing __gt__(1, 2)result of a > b: False
2、比对序:cmp_to_key() 将老式比较函数转换为返回比对键的函数
在 Python2 中,类可以定义一个__cmp__()
方法,该方法会根据这个对象小于、等于或者大于所比较的元素而分别返回 -1、0 或 1。Python3 废弃了__cmp__()
方法,sort()
之类的函数也不再支持cmp
参数。
使用cmp_to_key()
方法可以将较老的比较函数转换为返回比对键 (collation key) 的函数,进而支持sort()
之类的函数。
class MyObject:def __init__(self, val):self.val = val# __str__ 用于定义对象被转换成字符串时的行为# 当使用 str() 或 print() 函数时,会自动调用该对象所属类的 __str__ 方法def __str__(self):return 'MyObject({})'.format(self.val)# 较老的比较函数
def compare_obj(a, b):print('comparing {} and {}'.format(a, b))if a.val < b.val:return -1elif a.val > b.val:return 1return 0# 返回比对键的函数
get_key = functools.cmp_to_key(compare_obj)def get_key_wrapper(o):new_key = get_key(o)print('key_wrapper({}) -> {!r}'.format(o, new_key))return new_keyobjs = [MyObject(x) for x in [1, 3, 2, 8, 4]]# 先把键转换完成,再进行比较
# 比较时本质上还是会调用较老的比较函数,所以会有print输出
for o in sorted(objs, key=get_key_wrapper):print(o)>>> 输出结果:key_wrapper(MyObject(1)) -> <functools.KeyWrapper object at 0x104b210f0>key_wrapper(MyObject(3)) -> <functools.KeyWrapper object at 0x104b21120>key_wrapper(MyObject(2)) -> <functools.KeyWrapper object at 0x104b212a0>key_wrapper(MyObject(8)) -> <functools.KeyWrapper object at 0x104b221a0>key_wrapper(MyObject(4)) -> <functools.KeyWrapper object at 0x104b02b60>comparing MyObject(3) and MyObject(1)comparing MyObject(2) and MyObject(3)comparing MyObject(1) and MyObject(3)comparing MyObject(2) and MyObject(3)comparing MyObject(2) and MyObject(1)comparing MyObject(8) and MyObject(2)comparing MyObject(8) and MyObject(3)comparing MyObject(4) and MyObject(3)comparing MyObject(4) and MyObject(8)MyObject(1)MyObject(2)MyObject(3)MyObject(4)MyObject(8)
三、缓存
1、lru_cache():将函数历史调用结果放在缓存中,直接读取
lru_cache()
修饰符会将函数包装在一个「最近最少使用」缓存中,在缓存中形成一种映射关系:键为函数传参,值为函数结果。在后续的调用中如果有相同参数,就会直接从缓存获取结果,而不会再次调用函数。
@functools.lru_cache()
def expensive(a, b):print('expensive({}, {})'.format(a, b))return a * bMAX = 2print('First set of calls:')
for i in range(MAX):for j in range(MAX):expensive(i, j)# 第二次调用时有相同的参数值在缓存中,所以不会去真正调用函数
print('\nSecond set of calls:')
for i in range(MAX + 1):for j in range(MAX + 1):expensive(i, j)>>> 输出结果:First set of calls:expensive(0, 0)expensive(0, 1)expensive(1, 0)expensive(1, 1)Second set of calls:expensive(0, 2)expensive(1, 2)expensive(2, 0)expensive(2, 1)expensive(2, 2)
2、cache_info() 检查缓存状态 & cache_clear() 清空缓存
lru_cache()
修饰符为所修饰的函数增加了两个方法:
cache_clear()
:清空缓存,再传参时会真正调用函数,因为缓存中已经没有任何结果cache_info()
:检查缓存的状态,返回的结果中包含4个字段,所有字段的取值都是不断累加的,支持调用cache_info()
清空缓存:
字段 | 含义 |
---|---|
hits | 命中缓存的次数 |
misses | 未命中缓存的次数 |
maxsize | 缓存能够存储不同参数组合的计算结果的最大个数 |
currsize | 当前缓存的实际大小 |
print(expensive.cache_info())print('\nClearing cache:')
expensive.cache_clear()
print(expensive.cache_info())print('\nThird set of calls:')
for i in range(MAX):for j in range(MAX):expensive(i, j)
print(expensive.cache_info())>>> 输出结果:CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)Clearing cache:CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)Third set of calls:expensive(0, 0)expensive(0, 1)expensive(1, 0)expensive(1, 1)CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
3、maxsize 参数:控制缓存的最大容量
默认每个「最近最少使用」缓存最多能够存储 128 个不同参数组合的计算结果,可以通过maxsize
参数来控制这个大小。
注意,如果使用了缓存中的结果,会把这一项映射关系放在最新,而非原来的位置。
@functools.lru_cache(maxsize=2)
def expensive(a, b):print('called expensive({}, {})'.format(a, b))return a * bdef make_call(a, b):print('({}, {})'.format(a, b), end=' ')pre_hits = expensive.cache_info().hitsexpensive(a, b)post_hits = expensive.cache_info().hitsif post_hits > pre_hits:print('cache hit')# 建立缓存
make_call(1, 2)
make_call(2, 3)# 使用缓存结果,并把 (1, 2) 映射关系放在最新
make_call(1, 2)# 调用函数
make_call(3, 4)# (1, 2) 对应的结果仍然在缓存中
make_call(1, 2)# (2, 3) 对应的结果不在缓存中
make_call(2, 3)
4、lru_cache() 所修饰函数的参数必须 hashable
lru_cache()
所修饰函数的参数必须是可散列的(hashable),如果参数中传入不能散列的对象,会触发TypeError
异常。
@functools.lru_cache()
def expensive(a, b):print('called expensive({}, {})'.format(a, b))return a * b# TypeError: unhashable type: 'list'
expensive([1], 2)
四、缩减数据集
1、reduce():可迭代对象的累积计算,生成单个值
functools
模块中的reduce
方法用于对可迭代对象中的元素进行累积计算,最终得到一个单一结果。它的基本语法是reduce(可调用对象, 可迭代对象)
:
def do_reduce(a, b):print('do_reduce({}, {})'.format(a, b))return a + bresult = functools.reduce(do_reduce, range(1, 5))
print(result)>>> 输出结果:do_reduce(1, 2)do_reduce(3, 3)do_reduce(6, 4)10
2、第三个参数:为可迭代对象提供基础值
可以向reduce()
方法中传入第三个参数,这个参数用来初始化reduce()
方法计算的值,也就是作为可迭代对象的基础值,放在对象的最前面。
def do_reduce(a, b):print('do_reduce({}, {})'.format(a, b))return a + bresult = functools.reduce(do_reduce, range(1, 5), 99)
print(result)>>> 输出结果:do_reduce(99, 1)do_reduce(100, 2)do_reduce(102, 3)do_reduce(105, 4)109
需要注意可迭代对象和基础值参数的联合使用:
- 没有基础值参数时,可迭代对象的元素个数至少为1,若为空会触发
TypeError
异常 - 存在基础值参数时,可迭代对象允许为空
def do_reduce(a, b):return a + b# 1
functools.reduce(do_reduce, [1])# 100
functools.reduce(do_reduce, [1], 99)# 99
functools.reduce(do_reduce, [], 99)# TypeError: reduce() of empty iterable with no initial value
functools.reduce(do_reduce, [])
五、泛型函数 generic function
1、singledispatch():根据第一个参数的类型自动选择要调用的函数
functools
模块中的singledispatch()
修饰符用来注册一组泛型函数(generic function),可以根据函数第一个参数的类型自动选择要调用的函数。
singledispatch()
所修饰的函数会拥有register()
属性,该属性相当于另一个修饰符,用于注册替代实现。也就是说,我们可以向该属性传递一个类型,如果singledispatch()
所修饰函数的第一个参数属于这个类型,那么在调用函数时就会自动切换。
singledispatch()
所修饰的函数是默认实现,如果第一个参数的类型未被register()
修饰符指定,就使用这个默认实现。
@myfunc.register(int)
def myfunc_int(arg):print('myfunc_int({})'.format(arg))@myfunc.register(list)
def myfunc_list(arg):print('myfunc_list({})'.format(arg))# default myfunc('string argument')
myfunc('string argument')
# myfunc_int(1)
myfunc(1)
# default myfunc(2.3)
myfunc(2.3)
# myfunc_list(['a', 'b', 'c'])
myfunc(['a', 'b', 'c'])
2、如果类型不完全匹配,会基于继承顺序选择函数
没有找到这个类型的完全匹配时,会计算继承顺序,调用最接近的匹配类型所对应的函数,如class
会基于类层次结构切换函数。
class A:passclass B(A):passclass C(A):passclass D(B):passclass E(C, D):passclass F:pass@functools.singledispatch
def myfunc(arg):print('default myfunc({})'.format(arg.__class__.__name__))@myfunc.register(A)
def myfunc_A(arg):print('myfunc_A({})'.format(arg.__class__.__name__))@myfunc.register(B)
def myfunc_B(arg):print('myfunc_B({})'.format(arg.__class__.__name__))@myfunc.register(C)
def myfunc_C(arg):print('myfunc_C({})'.format(arg.__class__.__name__))myfunc(A())
myfunc(B())
myfunc(C())
myfunc(D())
myfunc(E())
myfunc(F())>>> 输出结果:myfunc_A(A)myfunc_B(B)myfunc_C(C)myfunc_B(D)myfunc_C(E)default myfunc(F)