python模块之functools

时间:2022-06-22
本文章向大家介绍python模块之functools,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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()时提供的关键字参数