python -- functools 模块学习

本文介绍了Python的functools模块,它用于高阶函数,包含函数装饰器和便捷功能函数。如cmp_to_key可转换老式比较函数,partial能为函数部分参数指定值,lru_cache可缓存函数调用结果等,还说明了各函数的作用、使用场景及注意事项。

functools是为了高阶函数,一般来说,任何可调用的对象在该模块中都可被当做函数而处理,主要包含了一些函数的装饰器和便捷的功能函数。

import functools
print([e for e in dir(functools) if not e.startswith('_')])

['MappingProxyType', 'RLock', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'WeakKeyDictionary', 'cmp_to_key',
 'get_cache_token', 'lru_cache', 'namedtuple', 'partial', 'partialmethod', 'recursive_repr', 'reduce', 
 'singledispatch', 'total_ordering', 'update_wrapper', 'wraps']

functools. cmp_to_key(func)

把老式的比较函数转换为关键字函数。它作为工具被使用,接受关键字函数(例如:sorted()、min()、max()、itertools. groupby())。该函数主要作为一个转化工具而存在,它将Python2中支持使用的比较函数转为现行可用的。但是在 python3 中已经不支持老式的比较函数了。所以简单知道存在即可。

functools. partial(func, *args, **keywords) --- 为 func 函数的部分参数指定参数值,设置初始值或改变返回值。从而得到一个转换后的函数,进而以后调用,就可以少传值。

该函数的返回partial对象,其中包含3个只读属性:

  •  partial.func
  •  partial.args
  •  partial.keywords
import functools
def add(a, b):
    return a + b
plus3 = functools.partial(add, 3)
print(plus3(4))  # 7
print(plus3.func,plus3.args,plus3.keywords)  # <function add at 0x0000000001D11EA0> (3,) {}

一个很实用的例子:可以在 json_serial_fallback 函数中添加类型判断来指定如何 json 序列化一个 Python 对象

import functools,datetime,json
def json_serial_fallback(obj):
    """JSON serializer for objects not serializable by default json code"""
    if isinstance(obj, (datetime.datetime, datetime.date)):
        return str(obj)
    if isinstance(obj, bytes):
        return obj.decode("utf-8")
    raise TypeError ("%s is not JSON serializable" % obj)
json_dumps = functools.partial(json.dumps, default=json_serial_fallback)

functools.reduce(function, sequence, initial=None)

在 Python2 中等同于内建函数 reduce,但是在 Python3 中内建的 reduce 函数被移除,只能使用 functools.reduce。该函数的作用是将一个序列通过函数处理后归纳为一个输出。

import functools
def add(x,y):
    return x+y
a=functools.reduce(add,range(1,3),4)
print(a)  # 7    ---  4+1+2
import functools
def double(x):
    return x * 2
def inc(x):
    return x + 1
def dec(x):
    return x - 1
def compose(*functions):
    return functools.reduce(lambda f,g: lambda x: f(g(x)),functions)  --- 高阶用法,递归了

#  1、functions 代表输出参数有 N 函数, f 第一次是取第 N-1 个函数,g 是取第 N 个函数,
#  2、step1 计算完结果作为第 N-2 个函数的输入,依次类推。当 f(g(x)) 改为g(f(x)) 时 f,g 从第0个函数开始取,相当于倒过来。

inc_double_and_dec = compose(dec,double,inc)(10)
print(inc_double_and_dec)   #  21

functools.lru_cache(maxsize=128,typed=False)

这个装饰器是在 Python3 中新加的,用于缓存函数的调用结果,对于需要多次调用的函数,而且每次调用参数都相同,则可以用该装饰器缓存调用结果,从而加快程序运行,避免传入相同的参数重复计算。maxsize : 缓存占用的最大字节,typed:设置将不同的缓存结果分开存放。

import functools
@functools.lru_cache(None)
def add(x, y):
    print("calculating: %s + %s" % (x, y))
    return x + y
print(add(1, 2))
print(add(1, 2)) # 直接返回缓存信息
print(add(2, 3))
# calculating: 1 + 2
# 3
# 3    -------  直接返回缓存信息
# calculating: 2 + 3
# 5

由于该装饰器会将不同的调用结果缓存在内存中,因此需要注意内存占用问题,避免占用过多内存,从而影响系统性能。

functools.total_ordering

这个一个类装饰器,用于为类自动生成比较方法,如果一个类实现了 __lt____le____gt____ge__ 这些方法中的至少一个(最好是:__eq()__ ),该装饰器会自动实现其他几个方法。

from functools import total_ordering
@total_ordering
class Student:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname
    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))
stu = Student("Huoty", "Kong")
stu2 = Student("Huoty", "Kong")
stu3 = Student("Qing", "Lu")
print(stu == stu2)
print(stu>stu3)
# [ '__eq__', '__ge__',  '__gt__', '__le__', '__lt__',.......]
# True
# False
from functools import total_ordering
@total_ordering
class User:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'User[name=%s'% self.name
    def _is_valid_operand(self,other):  # 根据是否有 name 属性来决定是否可以比较
        return hasattr(other,"name")
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return self.name.lower() == other.name.lower()
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return self.name.lower()<other.name.lower()
print(User.__gt__)  # <function _gt_from_lt at 0x00000000028437B8>   --- 产生于 lt 
# 如果将 @ 给注释掉,结果为:<slot wrapper '__gt__' of 'object' objects>,产生于 父类:object。

functools.singledispatch

单分发器,Python 3.4 新增,用于实现泛型函数,由一个单一参数的类型来决定调用哪个函数,用于实现函数对多个类型进行重载。

比如:同样的函数名称,为不同的参数类型提供不同的功能实现,本质就是根据参数变换将函数调向不同的函数。

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say", end=" ")
    print(arg)

# 使用 @singledispatch 装饰器修饰之后就会有 register()方法,用于为指定类型注册被转向调用的函数
@fun.register(int)   # 限定 fun 函数的第一个参数为 int 类型的函数版本
def _(arg, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)
        
@fun.register(float)       # 还可以支持多个类型绑定到同一个被转向函数
@fun.register(Decimal)
def _(arg, verbose=False):
    if verbose:
        print("支持多个类型绑定到同一个被转向函数")
    print(arg)

fun("Hello world.")    # Hello world.
fun(18)                  # 18

fun(["a", "b"],True)
# Enumerate this:
# 0 a
# 1 b

fun(2.4,True)
# 支持多个类型绑定到同一个被转向函数
# 2.4

functools.update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)

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

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")
    
hello()
# before call
# hello world
print(hello.__name__)
print(hello.__doc__)
# call_it
# wrap func: call_it     ----  应该打印的是 hello() 函数的,但是该函数没有这些属性。

 

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

@wrap2
def hello2():
    """test hello"""
    print('hello world2')
  
hello2()
# before call
# hello world2
print(hello2.__name__)
print(hello2.__doc__)
# hello2
# test hello

@functool.wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)

wraps 函数是为了在装饰器中方便的拷贝被装饰函数的签名,而对 update_wrapper 做的一个包装。

该函数装饰器用于修饰包装函数,使包装函数看上去就像 wrapped 函数。

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

functools.update_wrapper 和 @functool.wraps 的功能是一样的。只不过前者是函数,因此需要把包装函数作为第一个参数传入,而后者是函数装饰器,直接使用包装函数即可,无须将包装函数作为第一个参数传入。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值