functools 模块

参考:

functools 是 Python 函数式编程一个重要的内置库,但是内容实在不多。

主要的模块就几个

  • cmp_to_key
  • partial partialmethod
  • reduce
  • total_ordering
  • update_wrapper
  • wraps

cmp_to_key

在 list.sort 和 内建函数 sorted 中都有一个 key 参数,这个参数用来指定取元素的什么值进行比较,例如按字符串元素的长度进行比较

>>> x = ['hello','abc','iplaypython.com']
>>> x.sort(key=len)
>>> x
['abc', 'hello', 'iplaypython.com']

也就是说排序时会先对每个元素调用 key 所指定的函数,然后再排序。同时,sorted 和 list.sort 还提供了 cmp 参数来指定如何比较两个元素,但是在 Python 3 中该参数被去掉了。cmp_to_key 函数就是用来将老式的比较函数转化为 key 函数。用到 key 参数的函数还有 sorted(), min(), max(), heapq.nlargest(), itertools.groupby() 等

partial

本质上是一个装饰器,返回一个新的 partial 对象,当被调用时就和 func 一样,只不过通常参数会少于原 func 也就是提前给定原函数的一部分参数,再把它封装成另一个函数

原理大致如下

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

例子

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

partialmethod

Return a new partialmethod descriptor which behaves like partial except that it is designed to be used as a method definition rather than being directly callable.

一般用于方法定义,而不是直接调用

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

reduce

在 Python2 中等同于内建函数 reduce,但是在 Python3 中内建的 reduce 函数被移除,只能使用 functools.reduce

简单的说就是 reduce the sequence to a single value, function 必须是接受两个参数的,对一个序列一直执行,直直到最后变为一个值

其执行原理大概如下

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

例子,注意 reduce 还有一个默认参数 initializer

from functools import reduce
>>> function = lambda x, y: x+y
>>> iterable = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> reduce(function,  iterable)
45
>>> reduce(function,  iterable, 10)
55

total_ordering

这是一个类装饰器,给定一个类,这个类定义了一个或多个比较排序方法,这个类装饰器将会补充其余的比较方法,减少了自己定义所有比较方法时的工作量。

被修饰的类必须至少定义 lt(), le(), gt() 或 ge() 中的一个,同时,被修饰的类还应该提供 eq() 方法。

@total_ordering
class Student:
    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()))

update_wrapper

用 partial 包装的函数是没有 namedoc,这使得依赖于这些属性的代码可能无法正常工作。update_wrapper 可以拷贝被包装函数的 namemoduledocdict 属性到新的封装函数中去,其实现也非常简单

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))

    return wrapper

update_wrapper 主要用在装饰器函数中,以确保被装饰的保留原理的属性,调用 update_wrapper 需要至少两个参数,用起来不是很方便这可能是 wraps 封装出现的原因之一

def wrap(func):
    def call_it(*args, **kwargs):
        print "calling", func.__name__
        return func(*args, **kwargs)
    return call_it

@wrap
def hello():
    print "hello"

from functools import update_wrapper
def wrap2(func):
    def call_it(*args, **kwargs):
        print "calling", func.__name__
        return func(*args, **kwargs)
    return update_wrapper(call_it, func)

@wrap2
def hello2():
    print "hello2"


>>> print hello.__name__
call_it
>>> print hello2.__name__
hello2

wraps

wraps 函数是为了在装饰器中方便的拷贝被装饰函数的签名,而对 update_wrapper 做的一个包装,其实现如下:

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):

    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

示例

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