python笔记整理-11. functools

11.1 functools.partial

用于简化复杂的函数调用

partial主要用于将函数调用的时候给出默认的一些参数,有点像对象,先初始化完了以后调用方法,比如下面的这个例子:

import functools

def add(a, b): 
    return a + b

add(4, 2)
>>6

plus3 = functools.partial(add, 3)
plus5 = functools.partial(add, 5)

plus3(4)
>>7
plus3(7)
>>10
plus5(10)
>>15

a的值已经被固定了,每次调用的时候a都已经被传递了默认的值

那么partial重新包装了原来的函数,重新定义函数签名,返回了一个函数对象,并冻结了部分参数.

一个更加生动的例子

urlunquote = functools.partial(urlunquote, encoding='latin1')

# 当调用 urlunquote(*args, **kargs)
# 相当于 urlunquote(*args, **kargs, encoding='latin1')

还是一个例子,来自官方

basetwo = functools.partial(int, base=2)
basetwo.__doc__ = 'Convert base 2 string to an int.'

print(basetwo('10010'))
>> 18

11.2 functool.update_wrapper

functools.update_wrapper(wrapper, wrapped[, assigned][, updated])

让包装后的函数看起来更像被包装的函数,后面两个可选的参数assigned和updated的作用:可选的元祖参数用来决定在原始方法里的哪些属性被分配到包装里,以及哪些属性需要被更新,默认值在python的源码里是这样的:

# functools.py
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)

仅仅使用partial,函数会丢失一些东西,如name,doc 我们可以使用update_wrapper将这些复制到现有的partial对象

另外这个函数主要用在装饰器里

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'

from functools import update_wrapper
def wrap2(func):
    def call_it(*args, **kwargs):
        """wrap func: call_it2"""
        print 'before call'
        return func(*args, **kwargs)
    return update_wrapper(call_it, func)

@wrap2
def hello2():
    """test hello"""
    print 'hello world2'

if __name__ == '__main__':
    hello()
    print hello.__name__
    print hello.__doc__

    print
    hello2()
    print hello2.__name__
    print hello2.__doc__

结果如下:

before call
hello world
call_it
wrap func: call_it

before call
hello world2
hello2
test hello

这样保持了函数原有的属性.

11.3 functool.wraps

理解了上面2个,那么wraps就出现了,调用函数装饰器partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)的简写

from functools import wraps
def wrap3(func):
    @wraps(func)
    def call_it(*args, **kwargs):
        """wrap func: call_it2"""
        print 'before call'
        return func(*args, **kwargs)
    return call_it

@wrap3
def hello3():
    """test hello 3"""
    print 'hello world3'

print hello3.__name__
print hello3.__doc__

运行结果

before call
hello world3
hello3
test hello 3

一个完整的例子,用在django的drf-extension的cache里

# -*- coding: utf-8 -*-
from functools import wraps

from django.utils.decorators import available_attrs

from rest_framework_extensions.utils import get_cache
from rest_framework_extensions.settings import extensions_api_settings
from rest_framework_extensions.compat import six


class CacheResponse(object):
    def __init__(self,
                 timeout=None,
                 key_func=None,
                 cache=None,
                 cache_errors=None):
        if timeout is None:
            self.timeout = extensions_api_settings.DEFAULT_CACHE_RESPONSE_TIMEOUT
        else:
            self.timeout = timeout

        if key_func is None:
            self.key_func = extensions_api_settings.DEFAULT_CACHE_KEY_FUNC
        else:
            self.key_func = key_func

        if cache_errors is None:
            self.cache_errors = extensions_api_settings.DEFAULT_CACHE_ERRORS
        else:
            self.cache_errors = cache_errors

        self.cache = get_cache(cache or extensions_api_settings.DEFAULT_USE_CACHE)

    def __call__(self, func):
        this = self
        @wraps(func, assigned=available_attrs(func))
        def inner(self, request, *args, **kwargs):
            return this.process_cache_response(
                view_instance=self,
                view_method=func,
                request=request,
                args=args,
                kwargs=kwargs,
            )
        return inner

    def process_cache_response(self,
                               view_instance,
                               view_method,
                               request,
                               args,
                               kwargs):
        key = self.calculate_key(
            view_instance=view_instance,
            view_method=view_method,
            request=request,
            args=args,
            kwargs=kwargs
        )
        response = self.cache.get(key)
        if not response:
            response = view_method(view_instance, request, *args, **kwargs)
            response = view_instance.finalize_response(request, response, *args, **kwargs)
            response.render()  # should be rendered, before picklining while storing to cache

            if not response.status_code >= 400 or self.cache_errors:
                self.cache.set(key, response, self.timeout)

        if not hasattr(response, '_closable_objects'):
            response._closable_objects = []

        return response

    def calculate_key(self,
                      view_instance,
                      view_method,
                      request,
                      args,
                      kwargs):
        if isinstance(self.key_func, six.string_types):
            key_func = getattr(view_instance, self.key_func)
        else:
            key_func = self.key_func
        return key_func(
            view_instance=view_instance,
            view_method=view_method,
            request=request,
            args=args,
            kwargs=kwargs,
        )


cache_response = CacheResponse

cache_response 就是一个装饰器,那么这里使用了call方法来直接使用类当装饰器,
里面完全重写了方法的调用,并加入这个类里的一些方法,这样做有什么好处,可以把装饰器用到的一些方法和函数分离出来,参数在对象里共享,并且更面向对象.

11.4 functools.reduce

先看看reduce函数

reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。

例如,编写一个f函数,接收x和y,返回x和y的和:

def f(x, y):
    return x + y

调用 reduce(f, [1, 3, 5, 7, 9])时,reduce函数将做如下计算:

先计算头两个元素:f(1, 3),结果为4;
再把结果和第3个元素计算:f(4, 5),结果为9;
再把结果和第4个元素计算:f(9, 7),结果为16;
再把结果和第5个元素计算:f(16, 9),结果为25;
由于没有更多的元素了,计算结束,返回结果25。
上述计算实际上是对 list 的所有元素求和。虽然Python内置了求和函数sum(),但是,利用reduce()求和也很简单。

reduce()还可以接收第3个可选参数,作为计算的初始值。如果把初始值设为100,计算:

reduce(f, [1, 3, 5, 7, 9], 100)

结果将变为125,因为第一轮计算是:

计算初始值和第一个元素:f(100, 1),结果为101。

用这个的原因是使代码更兼容(python3)

11.5 functools.cmp_to_key

主要使用在需要key函数的地方,将老的cmp函数转换成key函数

11.6 functools.total_ordering

这个装饰器是在python2.7的时候加上的,它是针对某个类如果定义了ltlegtge这些方法中的至少一个,使用该装饰器,则会自动的把其他几个比较函数也实现在该类中.

@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()))
print dir(Student)

将会自动得到

['__doc__', '__eq__', '__ge__', '__gt__', '__le__', '__lt__', '__module__']

参考: