functools 模块提供了某些高阶函数(high-order function)。
functools.cmp_to_key(func)
比较函数是接收两个参数进行比较的函数,返回一个负数表示 <,返回 0 表示 =,返回一个正数表示 >。key 函数接收一个参数并返回另一个值作为进行排序的键。将比较函数转换为 key 函数,常在用到 key 关键字的函数如 sotred(), min(), heapq.nlargest(), itertools,groupby()中使用。cmp_to_key 主要作为过渡工具将 python2 中支持的比较函数进行转换。
def numeric_compare(x, y):
return x – y
# python2
print(sorted([5, 2, 4, 1, 3], cmp=numeric_compare))
# python3
print(sorted([5, 2, 4, 1, 3], key=cmp_to_key(numeric_compare)))
@functools.lru_cache(maxsize=128, typed=False)
使用 memoize 包装函数,保存最近 maxsize 次的函数调用结果。在 I /O-bound 函数上装饰,处理相同参数时可以节省函数执行时间。
如果 maxsize 为 None,会禁用 LRU 缓存特性且缓存大小会无限增长。maxsize 设置为 2 的 n 次方时性能最优。
如果 typed 为 True,不同类型函数参数的执行结果会被分别缓存,例如 f(3)和 f(3.0)会被视为有两个不同结果的不同调用。
因为该装饰器使用字典缓存函数执行结果,所以函数的位置参数和关键字参数必须是可哈希的。
不同的参数模式可能会被视为不同的缓存实体。例如 f(a=1, b=2)和 f(b=2, a=1)虽然只是关键字顺序不同但可能有两个单独的缓存实体。
被包装的函数可以调用 cache_info(),它返回一个 (hits, misses, maxsize, currsize) 形式的命名元组;可以调用 cache_clear()清空或使缓存无效;还可以调用__wrapped__属性绕过缓存,访问原始的底层函数。
LRU(least recently used)缓存通常应仅用在需要重用先前计算的值的场景,其他场景如使用 LRU 有不良影响、每次调用需要返回不同结果、time()、random()等应禁止使用。maxsize 表示缓存大小限制,确保不会无限制增长。
@lru_cache(maxsize=32)
def get_pep(num):
‘Retrieve text of a Python Enhancement Proposal’
resource = ‘http://www.python.org/dev/peps/pep-%04d/’ % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return ‘Not Found’
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
… pep = get_pep(n)
… print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
@functools.total_ordering
类装饰器,包装在定义了一个或多个富比较排序方法的类上,会补足其他的比较方法。该类必须定义__lt__(), __le__(), __gt__(), 或__ge__()中至少一个方法,并建议额外定义__eq__()方法。
@total_ordering
class Student:
def __init__(self, lastname, firstname):
self.lastname = lastname
self.firstname = firstname
def _is_valid_operand(self, other):
return (hasattr(other, “lastname”) and
hasattr(other, “firstname”))
def __eq__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
NOTE:使用这个装饰器的代价是,装饰器自动补足的比较方法耗费了更多的执行时间并拥有更复杂的堆栈跟踪。如果应用性能基准测试表明是使用此装饰器造成的瓶颈,手动实现所有六种富比较方法可以带来直观的速度提升。
functools.partial(func, args, *keywords)
偏函数,返回一个 partial 对象,调用时等同调用了使用预设位置参数和关键字参数的 func 函数。如果 partial 对象调用时又提供了额外的位置参数,追加到 args,如果提供了额外的关键字参数,扩展 keywords 并覆盖相同的键值。
大致等同于:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
partial()用于“冻结”函数的部分位置参数和 / 或关键字参数而产生一个代表某部分函数功能的简化标志。
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = ‘Convert base 2 string to an int.’
>>> basetwo(‘10010’)
18
class functools.partialmethod(func, args, *keywords)
后续补充
functools.reduce(function, iterable[, initializer])
将可迭代对象 iterable 中的元素从左到右累计到接收两个参数的函数 func 中。例如 reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])的计算等同于((((1+2)+3)+4)+5)。如果设置了可选参数 initializer,它被放置在要计算的序列之前,并在序列为空时作为默认值;如果未提供 initializer 的值且序列仅包含一个元素,返回该元素。
@functools.singledispatch
将函数转换为单分派泛函数(single-dispatch generic function)。
使用 @singledispatch 装饰器定义一个泛函数,单分派仅限于第一个参数的类型:
from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print(“Let me just say,”, end=” “)
print(arg)
使用泛函数的 register()属性添加重载实现,该属性是一个装饰器。对于使用类型注解的函数,该装饰器将自动推断第一个参数的类型:
@fun.register
def _(arg: int, verbose=False):
if verbose:
print(“Strength in numbers, eh?”, end=” “)
print(arg)
@fun.register
def _(arg: list, verbose=False):
if verbose:
print(“Enumerate this:”)
for i, elem in enumerate(arg):
print(i, elem)
如果不使用类型注解,可以显式地给装饰器传递类型参数:
@fun.register(complex)
def _(arg, verbose=False):
if verbose:
print(“Better than complicated.”, end=” “)
print(arg.real, arg.imag)
也可以以函数的形式使用 register()注册 lambda 函数和已定义的函数:
def nothing(arg, verbose=False):
print(“Nothing.”)
fun.register(type(None), nothing)
register()属性返回允许装饰器堆叠、序列化以及创建独立的单元测试的未装饰的函数:
from decimal import Decimal
@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
if verbose:
print(“Half of your number:”, end=” “)
print(arg / 2)
>>> fun_num is fun
False
调用时,泛函数只分派第一个参数的类型:
>>> fun(“Hello, world.”)
Hello, world.
>>> fun(“test.”, verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun([‘spam’, ‘spam’, ‘eggs’, ‘spam’], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615
如果某个类型没有相应的实现,将查找更通用的实现进行解析。使用 @singledispatch 装饰的原始函数注册为 object 类型,将在没有更好实现的情况下使用。调用 dispatch()属性查看泛函数使用了哪个实现:
>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict) # note: default implementation
<function fun at 0x103fe0000>
调用 registry 属性查看注册的所有实现:
>>> fun.registry.keys()
dict_keys([<class ‘NoneType’>, <class ‘int’>, <class ‘object’>, <class ‘decimal.Decimal’>, <class ‘list’>, <class ‘float’>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
更新包装函数(wrapper),使其看起来更像被包装的原始函数(wrapped)。元组类型的可选参数 assigned 指定 wrapped 函数的哪些属性直接分派到 wrapper 函数对应的属性上,updated 参数指定使用 wrapped 函数的哪些属性更新 wrapper 函数对应的属性上。
WRAPPER_ASSIGNMENTS 的默认值是 ’__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’,’__annotations__’WRAPPER_UPDATES 的默认值是 ’__dict__’
通过访问包装函数的__wrapped__属性可以获取到被包装的原始函数。
该函数主要用于装饰器使用场景下,如果不更新包装函数,返回函数的元数据将指向包装函数而非被包装的原始函数,一般来说没太大意义。
def wrapper(f):
def wrapper_function(*args, **kwargs):
“””this is a wrapper function”””
return f(*args, **kwargs)
# update_wrapper(wrapper_function, f)
return wrapper_function
@wrapper
def wrapped():
“””this is a wrapped function”””
pass
print(wrapped.__doc__)
print(wrapped.__name__)
# 不使用 update_wrapper 的结果:
>>> this is a wrapper function
>>> wrapper_function
# 使用 update_wrapper 的结果:
>>> this is a wrapped function
>>> wrapped
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
等同于 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated),不过是作为包装函数定义时的装饰器使用。
def wrapper(f):
@wraps(f)
def wrapper_function(*args, **kwargs):
“””this is a wrapper function”””
return f(*args, **kwargs)
return wrapper_function
@wrapper
def wrapped():
“””this is a wrapped function”””
pass
print(wrapped.__doc__)
print(wrapped.__name__)
partial 对象
partial 对象是由 partial()方法创建的可调用对象,它有三个只读属性:partial.func 一个可调用对象或函数。调用 partial 对象将使用新的位置参数和关键字参数转发到 func。partial.args 调用 partial()时提供的位置参数 partial.keywords 调用 partial()时提供的关键字参数