Python 中高级知识 functools

functools 概述

上一节,我们讲了python怎么实现函数的重载,使用的是functools里面的一个装饰器。现在我就functools做一个大概的全局介绍

整体

源码:

"""functools.py - Tools for working with functions and callable objects
"""
# Python module wrapper for _functools C module
# to allow utilities written in Python to be added
# to the functools module.
# Written by Nick Coghlan <ncoghlan at gmail.com>,
# Raymond Hettinger <python at rcn.com>,
# and Łukasz Langa <lukasz at langa.pl>.
#   Copyright (C) 2006-2013 Python Software Foundation.
# See C source code for _functools credits/copyright
 
__all__ = ['update_wrapper''wraps''WRAPPER_ASSIGNMENTS''WRAPPER_UPDATES',
           'total_ordering''cmp_to_key''lru_cache''reduce''partial',
           'partialmethod''singledispatch']

functools是一个对函数(function)或者可调用对象(callable object)进行操作的工具集,主要是对我们开发者写的一些函数和可调用对象进行功能强化

里面标注了这些方法给开发者

__all__ = ['update_wrapper''wraps''WRAPPER_ASSIGNMENTS''WRAPPER_UPDATES',
           'total_ordering''cmp_to_key''lru_cache''reduce''partial',
           'partialmethod''singledispatch']

我翻译下就是:

名称

类型

功能

update_wrapper

 

装饰器相关功能

wraps

 

装饰器相关功能

WRAPPER_ASSIGNMENTS

 

装饰器相关功能

WRAPPER_UPDATES

 

装饰器相关功能

total_ordering

方法

对比较的自动填充

cmp_to_key

方法(底层C lang)

两个数比较

lru_cache

方法

方法缓存

reduce

方法(底层C lang)

python的reduce计算

partial

柯里化

partialmethod

柯里化

singledispatch

函数,装饰器

单入参类型调度,也就是根据第一个入参的类型进行任务调度

singledispatch

已讲

 

partial

柯里化,细节可以百度下

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
 
 
from functools import partial
 
 
def sum(a: int, b: int) -> int:
    return a + b
 
 
add1 = partial(sum, 1)
print(add1(2))

partialmethod

对于python偏函数partial理解运用起来比较简单,就是对原函数某些参数设置默认值,生成一个新函数。而如果对于类方法,因为第一个参数是self,使用partial就会报错了。对此,python3.4新引入了partialmethod

 

class Cell(object):
    def __init__(self):
        self._alive = False
    @property
    def alive(self):
        return self._alive
    # 这里是类的实例方法,有self,partial就无法使用了,我们可以使用partialmethod
    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

 

Python的函数式编程中,map/reduce 算法

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import reduce
 
l1 = [123]
l2 = reduce(lambda a, b: a + b, l1)
print(l2)

 

lru_cache

 

def lru_cache(maxsize=128, typed=False):
    """Least-recently-used cache decorator.
 
    If *maxsize* is set to None, the LRU features are disabled and the cache
    can grow without bound.
 
    If *typed* is True, arguments of different types will be cached separately.
    For example, f(3.0) and f(3) will be treated as distinct calls with
    distinct results.
 
    Arguments to the cached function must be hashable.
 
    View the cache statistics named tuple (hits, misses, maxsize, currsize)
    with f.cache_info().  Clear the cache and statistics with f.cache_clear().
    Access the underlying function with f.__wrapped__.
 
    See:  <http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used>
 
    """

这个有很有用,代码演示下,类似于Java的redis+AOP,ehcache

 

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import lru_cache
 
# 缓存大小为2,默认不管type
@lru_cache(maxsize=2)
def my_print(s: str) -> str:
    print("my_print: ", s)
    return s
 
 
a = my_print("hello python")
# 无需执行
b = my_print("hello python")
c = my_print("hello java")
# 无需执行
d = my_print("hello java")
print("a= ", a, "b= ", b, "c= ", c)
 
# 前面缓存满了,这个执行后挤走了"hello python"
e = my_print("hello js")
# 无需执行
f = my_print("hello js")
# 这时 缓存里面有 hello java 和 hello js,下面的2个语句执行的效果不一样
#g = my_print("hello java")
g = my_print("hello python")

 

效果

my_print:  hello python
my_print:  hello java
a=  hello python b=  hello python c=  hello java
my_print:  hello js
my_print:  hello python

cmp_to_key

类似于java的compare函数

  • python2 --> cmp

  • python3 --> cmp_to_key

cmp_to_key 是对 cmp 的取代

旧的sort函数接口接受一个类似于C的比较函数,根据两个参数的大小不同返回正、负或者0。新版本改用了key参数,将每个元素映射到一个key,然后用key的比较代替原来元素的比较,许多时候这样更方便,但是真的需要旧的比较函数接口的时候怎么办呢?这就需要cmp_to_key了,它接受一个比较函数,然后返回一个函数对象,这个函数调用后会为每个元素构造一个对象,这个返回对象作为key的时候,排序的结果,和使用比较函数是一样的。它相当于旧接口和新接口的转换器。什么原理呢?其实很巧妙,Python对象的比较过程可以用__lt__等函数重写,cmp_to_key的返回值也是一个重写了比较过程的对象,在调用自定义比较过程时会调用输入的比较函数,从而实现了设计目标。

 

一般和sort函数搭配使用,来排序

比如:

我们找=计算出一个list里面全部元素的能组合出的最大的数

最大的数

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import cmp_to_key
 
s = ['12''123''4']
 
print(''.join(sorted(s, key=cmp_to_key(lambda x, y: int(y + x) - int(x + y)))))

最小的数

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import cmp_to_key
 
s = ['12''123''4']
 
print(''.join(sorted(s, key=cmp_to_key(lambda x, y: int(y + x) - int(x + y)), reverse=True)))

 

total_ordering

 

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

被修饰的类必须至少定义 __lt__(), __le__(),__gt__(),__ge__()中的一个,同时,被修饰的类还应该提供 __eq__()方法。

 

from functools import total_ordering
 
class Person:
    # 定义相等的比较函数
    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()))
 
p1 = Person()
p2 = Person()
 
p1.lastname = "123"
p1.firstname = "000"
 
p2.lastname = "1231"
p2.firstname = "000"
 
print p1 < p2
print p1 <= p2
print p1 == p2
print p1 > p2
print p1 >= p2

 

控制台输出,

 

True
True
False
False
False

 

update_wrapper

原码

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function
 
       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """

这个在开发者自己写装饰器的时候经常用到

更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数。

可选的参数指定了被包裹函数的哪些属性直接赋值给包裹函数的对应属性,同时包裹函数的哪些属性要更新而不是直接接受被包裹函数的对应属性,参数assigned的默认值对应于模块级常量WRAPPER_ASSIGNMENTS(默认地将被包裹函数的 __name__, __module__,和 __doc__ 属性赋值给包裹函数),参数updated的默认值对应于模块级常量WRAPPER_UPDATES(默认更新wrapper函数的 dict 属性)。

这个函数的主要用途是在一个装饰器中,原函数会被装饰(包裹),装饰器函数会返回一个wrapper函数,如果装饰器返回的这个wrapper函数没有被更新,那么它的一些元数据更多的是反映wrapper函数定义的特征,无法反映wrapped函数的特性。

 

使用比 wraps 麻烦点

 

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from functools import update_wrapper
 
 
def my_sum(a: int, b: int) -> int:
    return a + b
 
 
def wapp_func(func):
    def wapp(*args, **kw):
        print("wapping")
        print(args)
        return func(*args)
 
    return update_wrapper(wapp, func)
 
 
my_sum_awp = wapp_func(my_sum)
sum_num = my_sum_awp(12)
print(sum_num)

 

wraps

这个函数可用作一个装饰器,简化调用update_wrapper的过程,调用这个函数等价于调用partial(update_wrapper, wrapped = wrapped, assigned = assigned,updated = updated)

 

from functools import wraps
 
def my_decorator(f):
    @wraps(f)
    def wrapper(*args,**kwds):
        print "Calling decorated function"
        return f(*args,**kwds)
    return wrapper
 
@my_decorator
def example():
    """DocString"""
    print "Called example function"
 
example()
print example.__name__
print example.__doc__

 

控制台输出,

 

Calling decorated function
Called example function
example
DocString

 

可以看到,最终调用函数example时,是经过@my_decorator装饰的,装饰器的作用是接受一个被包裹的函数作为参数,对其进行加工,返回一个包裹函数,代码使用 @functools.wraps装饰将要返回的包裹函数wrapper,使得它的 __name__, __module__,和 __doc__属性与被装饰函数example完全相同,这样虽然最终调用的是经过装饰的example函数,但是某些属性还是得到维护。

如果在 @my_decorator的定义中不使用@function.wraps装饰包裹函数,那么最终example.__name__ 将会变成wrapper,而example.__doc__ 也会丢失。

将 @wraps(f)注释掉,然后运行程序,控制台输出,

 

Calling decorated function
Called example function
wrapper
None

 

比如,我们也是用了它

 

def rds_mysql_init_wrapper(func):
    """
    English:
        warp the func and return the wrapped function
        before func exec we init/check mysql connection and auto_commit after the func executed
    中文:
        将func包装并返回被包装后的函数
        在func执行前,自动初始化/检查MySQL连接,再函数执行后自动commit
 
    :param func: The function to be wrapped
    :return: wrapped function
    """
 
    @functools.wraps(func)
    def wrapper(*args, **kw):
        """
           initial aws rds mysql connection
 
           if mysql connection is not used before,we connect mysql at first time;
           else we try to reuse mysql connection.
 
           :return: mysql connection
           """
posted @ 2021-09-26 10:25  旁人X  阅读(114)  评论(0)    收藏  举报