functools是为了高阶函数,一般来说,任何可调用的对象在该模块中都可被当做函数而处理,主要包含了一些函数的装饰器和便捷的功能函数。
import functools
print([e for e in dir(functools) if not e.startswith('_')])
['MappingProxyType', 'RLock', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'WeakKeyDictionary', 'cmp_to_key',
'get_cache_token', 'lru_cache', 'namedtuple', 'partial', 'partialmethod', 'recursive_repr', 'reduce',
'singledispatch', 'total_ordering', 'update_wrapper', 'wraps']
functools. cmp_to_key(func)
把老式的比较函数转换为关键字函数。它作为工具被使用,接受关键字函数(例如:sorted()、min()、max()、itertools. groupby())。该函数主要作为一个转化工具而存在,它将Python2中支持使用的比较函数转为现行可用的。但是在 python3 中已经不支持老式的比较函数了。所以简单知道存在即可。
functools. partial(func, *args, **keywords) --- 为 func 函数的部分参数指定参数值,设置初始值或改变返回值。从而得到一个转换后的函数,进而以后调用,就可以少传值。
该函数的返回partial对象,其中包含3个只读属性:
- partial.func
- partial.args
- partial.keywords
import functools
def add(a, b):
return a + b
plus3 = functools.partial(add, 3)
print(plus3(4)) # 7
print(plus3.func,plus3.args,plus3.keywords) # <function add at 0x0000000001D11EA0> (3,) {}
一个很实用的例子:可以在 json_serial_fallback
函数中添加类型判断来指定如何 json 序列化一个 Python 对象
import functools,datetime,json
def json_serial_fallback(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime.datetime, datetime.date)):
return str(obj)
if isinstance(obj, bytes):
return obj.decode("utf-8")
raise TypeError ("%s is not JSON serializable" % obj)
json_dumps = functools.partial(json.dumps, default=json_serial_fallback)
functools.reduce(function, sequence, initial=None)
在 Python2 中等同于内建函数 reduce,但是在 Python3 中内建的 reduce 函数被移除,只能使用 functools.reduce
。该函数的作用是将一个序列通过函数处理后归纳为一个输出。
import functools
def add(x,y):
return x+y
a=functools.reduce(add,range(1,3),4)
print(a) # 7 --- 4+1+2
import functools
def double(x):
return x * 2
def inc(x):
return x + 1
def dec(x):
return x - 1
def compose(*functions):
return functools.reduce(lambda f,g: lambda x: f(g(x)),functions) --- 高阶用法,递归了
# 1、functions 代表输出参数有 N 函数, f 第一次是取第 N-1 个函数,g 是取第 N 个函数,
# 2、step1 计算完结果作为第 N-2 个函数的输入,依次类推。当 f(g(x)) 改为g(f(x)) 时 f,g 从第0个函数开始取,相当于倒过来。
inc_double_and_dec = compose(dec,double,inc)(10)
print(inc_double_and_dec) # 21
functools.lru_cache(maxsize=128,typed=False)
这个装饰器是在 Python3 中新加的,用于缓存函数的调用结果,对于需要多次调用的函数,而且每次调用参数都相同,则可以用该装饰器缓存调用结果,从而加快程序运行,避免传入相同的参数重复计算。maxsize : 缓存占用的最大字节,typed:设置将不同的缓存结果分开存放。
import functools
@functools.lru_cache(None)
def add(x, y):
print("calculating: %s + %s" % (x, y))
return x + y
print(add(1, 2))
print(add(1, 2)) # 直接返回缓存信息
print(add(2, 3))
# calculating: 1 + 2
# 3
# 3 ------- 直接返回缓存信息
# calculating: 2 + 3
# 5
由于该装饰器会将不同的调用结果缓存在内存中,因此需要注意内存占用问题,避免占用过多内存,从而影响系统性能。
functools.total_ordering
这个一个类装饰器,用于为类自动生成比较方法,如果一个类实现了 __lt__
、__le__
、__gt__
、__ge__
这些方法中的至少一个(最好是:__eq()__ ),该装饰器会自动实现其他几个方法。
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
print(dir(Student))
stu = Student("Huoty", "Kong")
stu2 = Student("Huoty", "Kong")
stu3 = Student("Qing", "Lu")
print(stu == stu2)
print(stu>stu3)
# [ '__eq__', '__ge__', '__gt__', '__le__', '__lt__',.......]
# True
# False
from functools import total_ordering
@total_ordering
class User:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'User[name=%s'% self.name
def _is_valid_operand(self,other): # 根据是否有 name 属性来决定是否可以比较
return hasattr(other,"name")
def __eq__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return self.name.lower() == other.name.lower()
def __lt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return self.name.lower()<other.name.lower()
print(User.__gt__) # <function _gt_from_lt at 0x00000000028437B8> --- 产生于 lt
# 如果将 @ 给注释掉,结果为:<slot wrapper '__gt__' of 'object' objects>,产生于 父类:object。
functools.singledispatch
单分发器,Python 3.4 新增,用于实现泛型函数,由一个单一参数的类型来决定调用哪个函数,用于实现函数对多个类型进行重载。
比如:同样的函数名称,为不同的参数类型提供不同的功能实现,本质就是根据参数变换将函数调向不同的函数。
from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say", end=" ")
print(arg)
# 使用 @singledispatch 装饰器修饰之后就会有 register()方法,用于为指定类型注册被转向调用的函数
@fun.register(int) # 限定 fun 函数的第一个参数为 int 类型的函数版本
def _(arg, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@fun.register(list)
def _(arg, verbose=False):
if verbose:
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
@fun.register(float) # 还可以支持多个类型绑定到同一个被转向函数
@fun.register(Decimal)
def _(arg, verbose=False):
if verbose:
print("支持多个类型绑定到同一个被转向函数")
print(arg)
fun("Hello world.") # Hello world.
fun(18) # 18
fun(["a", "b"],True)
# Enumerate this:
# 0 a
# 1 b
fun(2.4,True)
# 支持多个类型绑定到同一个被转向函数
# 2.4
functools.update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)
用 partial 包装的函数是没有 __name__ 和 __doc__,这使得依赖于这些属性的代码可能无法正常工作。update_wrapper 可以拷贝被包装函数的 __name__、__module__、__doc__ 和 __dict__属性到新的封装函数中去,其实现也非常简单:
def wrap(func):
def call_it(*args, **kwargs):
"""wrap func: call_it"""
print('before call')
return func(*args, **kwargs)
return call_it
@wrap
def hello():
"""say hello"""
print("hello world")
hello()
# before call
# hello world
print(hello.__name__)
print(hello.__doc__)
# call_it
# wrap func: call_it ---- 应该打印的是 hello() 函数的,但是该函数没有这些属性。
from functools import update_wrapper
def wrap2(func):
def call_it(*args, **kwargs):
print('before call')
return func(*args, **kwargs)
return update_wrapper(call_it, func)
@wrap2
def hello2():
"""test hello"""
print('hello world2')
hello2()
# before call
# hello world2
print(hello2.__name__)
print(hello2.__doc__)
# hello2
# test hello
@functool.wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)
wraps
函数是为了在装饰器中方便的拷贝被装饰函数的签名,而对 update_wrapper
做的一个包装。
该函数装饰器用于修饰包装函数,使包装函数看上去就像 wrapped 函数。
from functools import wraps
def wrap3(func):
@wraps(func)
def call_it(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return call_it
@wrap3
def hello3(func):
print("hello3")
print(hello3.__name__) # hello3
functools.update_wrapper 和 @functool.wraps 的功能是一样的。只不过前者是函数,因此需要把包装函数作为第一个参数传入,而后者是函数装饰器,直接使用包装函数即可,无须将包装函数作为第一个参数传入。