functools 作用于函数的函数

functools 模块提供用于调整或扩展函数和其他可调用对象的工具,而无需完全重写它们。

装饰器

partial 类是 functools 模块提供的主要工具, 它可以用来“包装”一个可调用的对象的默认参数。它产生的对象本身是可调用的,可以看作是原生函数。它所有的参数都与原来的相同,并且可以使用额外的位置参数或命名参数来调用。使用 partial 代替 lambda 来为函数提供默认参数,同时保留那些未指定的参数。

Partial 对象

下面列子是对 myfunc 方法的两个 partial 对象,show_details() 用于输出partial对象的 funcargskeywords 属性:

import functoolsdef myfunc(a, b=2):"""Docstring for myfunc()."""print('  传入参数:', (a, b))def show_details(name, f, is_partial=False):"""Show details of a callable object."""print('{}:'.format(name))print('  object:', f)if not is_partial:print('  __name__:', f.__name__)if is_partial:print('  func:', f.func)print('  args:', f.args)print('  keywords:', f.keywords)returnshow_details('myfunc', myfunc)
myfunc('a', 3)
print()# # 给'b'重新设置一个不同的默认参数
# # 调用时仍需提供参数'a'
p1 = functools.partial(myfunc, b=4)
show_details('partial 修改关键字参数', p1, True)
p1('传入 a')
p1('重写 b', b=5)
print()
#
# # 给 'a' 和 'b' 都设置默认参数.
p2 = functools.partial(myfunc, '默认 a', b=99)
show_details('partial 设置默认参数', p2, True)
p2()
p2(b='重写 b')
print()print('参数缺失时:')
p1()

示例中最后调用第一个 partial 对象而没有传递 a 的值,导致异常。

myfunc:object: <function myfunc at 0x00000180005077B8>__name__: myfunc传入参数: ('a', 3)partial 修改关键字参数:object: functools.partial(<function myfunc at 0x00000180005077B8>, b=4)func: <function myfunc at 0x00000180005077B8>args: ()keywords: {'b': 4}传入参数: ('传入 a', 4)传入参数: ('重写 b', 5)partial 设置默认参数:object: functools.partial(<function myfunc at 0x00000180005077B8>, '默认 a', b=99)func: <function myfunc at 0x00000180005077B8>args: ('默认 a',)keywords: {'b': 99}传入参数: ('默认 a', 99)传入参数: ('默认 a', '重写 b')参数缺失时:
Traceback (most recent call last):File "functools_partial.py", line 51, in <module>p1()
TypeError: myfunc() missing 1 required positional argument: 'a'

获取函数属性

默认情况下, partial 对象没有 __name____doc__ 属性。 这样不利于被装饰的函数进行调试。可以使用 update_wrapper() 从原函数复制或新增属性到 partial 对象。

import functoolsdef myfunc(a, b=2):"""Docstring for myfunc()."""print('  传入参数:', (a, b))def show_details(name, f):"""Show details of a callable object."""print('{}:'.format(name))print('  object:', f)print('  __name__:', end=' ')try:print(f.__name__)except AttributeError:print('(no __name__)')print('  __doc__', repr(f.__doc__))print()show_details('myfunc', myfunc)p1 = functools.partial(myfunc, b=4)
show_details('raw wrapper', p1)print('Updating wrapper:')
print('  assign:', functools.WRAPPER_ASSIGNMENTS)
print('  update:', functools.WRAPPER_UPDATES)
print()functools.update_wrapper(p1, myfunc)
show_details('updated wrapper', p1)

添加到装饰器的属性在 WRAPPER_ASSIGNMENTS 中定义,而 WRAPPER_UPDATES 列出要修改的值。

myfunc:object: <function myfunc at 0x000002315C123E18>__name__: myfunc__doc__ 'Docstring for myfunc().'raw wrapper:object: functools.partial(<function myfunc at 0x000002315C123E18>, b=4)__name__: (no __name__)__doc__ 'partial(func, *args, **keywords) - new function with partial application\n    of the given arguments and keywords.\n'Updating wrapper:assign: ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')update: ('__dict__',)updated wrapper:object: functools.partial(<function myfunc at 0x000002315C123E18>, b=4)__name__: myfunc__doc__ 'Docstring for myfunc().'

其他调用对象

partial适用于所有可调用可对象,并不是仅可用于独立函数。

import functoolsclass MyClass:"""Demonstration class for functools"""def __call__(self, e, f=6):"Docstring for MyClass.__call__"print('  called object with:', (self, e, f))def show_details(name, f):""""Show details of a callable object."""print('{}:'.format(name))print('  object:', f)print('  __name__:', end=' ')try:print(f.__name__)except AttributeError:print('(no __name__)')print('  __doc__', repr(f.__doc__))returno = MyClass()show_details('instance', o)
o('e goes here')
print()p = functools.partial(o, e='default for e', f=8)
functools.update_wrapper(p, o)
show_details('instance wrapper', p)
p()

上面例子使用 MyClass 类的实例的 __call__() 方法创建了partial对象。照样正常工作:

instance:object: <__main__.MyClass object at 0x000002DE7C2CD2E8>__name__: (no __name__)__doc__ 'Demonstration class for functools'called object with: (<__main__.MyClass object at 0x000002DE7C2CD2E8>, 'e goes here', 6)instance wrapper:object: functools.partial(<__main__.MyClass object at 0x000002DE7C2CD2E8>, e='default for e', f=8)__name__: (no __name__)__doc__ 'Demonstration class for functools'called object with: (<__main__.MyClass object at 0x000002DE7C2CD2E8>, 'default for e', 8)

方法和函数

partial() 返回一个可以直接调用的对象, partialmethod() 返回一个可调用的为某个对象准备的未绑定的方法。再下面例子中,同一个独立函数被两次添加到类 MyClass 属性。使用 partialmethod() 生成 method1(), partial() 生成 method2():

import functoolsdef standalone(self, a=1, b=2):"""独立函数"""print('  called standalone with:', (self, a, b))if self is not None:print('  self.attr =', self.attr)class MyClass:""""functools 示例类"""def __init__(self):self.attr = 'instance attribute'method1 = functools.partialmethod(standalone)method2 = functools.partial(standalone)o = MyClass()print('standalone')
standalone(None)
print()print('method1 as partialmethod')
o.method1()
print()print('method2 as partial')
try:o.method2()
except TypeError as err:print('ERROR: {}'.format(err))

method1() 可以被 MyClass 实例调用,和普通类方法一样,实例作为第一个参数传入。method2() 没有被成功绑定为类方法。因此其 self 参数必须显式传入,所以此例抛出 TypeError 异常:

standalonecalled standalone with: (None, 1, 2)method1 as partialmethodcalled standalone with: (<__main__.MyClass object at 0x00000214B4459B70>, 1, 2)self.attr = instance attributemethod2 as partial
ERROR: standalone() missing 1 required positional argument: 'self'

在装饰器中使用

使用装饰器时保持函数的属性信息有时非常有用。但是使用装饰器时难免会损失一些原本的功能信息。所以functools提供了 wraps() 装饰器可以通过 update_wrapper() 将原函数对象的指定属性复制给包装函数对象。

from functools import wrapsdef logged1(func):def with_login(*args, **kwargs):print(func.__name__ + "was called")return func(*args, **kwargs)return with_login@logged1
def f1(x):""" function doc"""return x + x * 1def logged2(func):@wraps(func)def with_login(*args, **kwargs):print(func.__name__ + "was called")return func(*args, **kwargs)return with_login@logged2
def f2(x):""" function doc """return x + x * 1print("不使用functools.wraps时:")
print("__name__:  " + f1.__name__)
print("__doc__:  ", end=" ")
print(f1.__doc__)
print()print("使用functools.wraps时:")
print("__name__:  " + f2.__name__)
print("__doc__:  ", end=" ")
print(f2.__doc__)
不使用functools.wraps时:
__name__:  with_login
__doc__:   None使用functools.wraps时:
__name__:  f2
__doc__:    function doc 

比较

在Python2之前,类中可以定义 __cmp__() 方法,该方法根据对象是否小于、d等于或大于被比较项返回-1、0或1。Python2.1开始引入了 富比较 方法API(__lt__(), __le()__, __eq__(), __ne__(), __gt__()__ge__()),用于执行比较操作返回一个布尔值。Python3中 __cmp__() 放弃支持这些新方法,由 functools 提供工具,以便于编写符合Python3中新的比较需求的类。

富比较

富比较API旨在允许具有复杂比较的类以最有效的方式实现每种计算。但是,对于比较相对简单的类,手动创建每种富比较方法没有意义。total_ordering() 类装饰器可以使被装饰的类只需要定义 __lt__(),__le__().__gt__()__ge__() 中的其中一个和 __eq__(), 剩下的由该装饰器自动提供。这简化了定义所有富比较操作的工作量。

import functools
import inspect
from pprint import pprint@functools.total_ordering
class MyObject:def __init__(self, val):self.val = valdef __eq__(self, other):print('  testing __eq__({}, {})'.format(self.val, other.val))return self.val == other.valdef __gt__(self, other):print('  testing __gt__({}, {})'.format(self.val, other.val))return self.val > other.valprint("MyObject's Methods:\n")
pprint(inspect.getmembers(MyObject, inspect.isfunction))a = MyObject(1)
b = MyObject(2)print('\nComparisons:')
for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:print('\n{:<6}:'.format(expr))result = eval(expr)print('  result of {}: {}'.format(expr, result))
MyObject's Methods:[('__eq__', <function MyObject.__eq__ at 0x0000021DE4DB4048>),('__ge__', <function _ge_from_gt at 0x0000021DDDE5D268>),('__gt__', <function MyObject.__gt__ at 0x0000021DE4DB40D0>),('__init__', <function MyObject.__init__ at 0x0000021DDDE877B8>),('__le__', <function _le_from_gt at 0x0000021DDDE5D2F0>),('__lt__', <function _lt_from_gt at 0x0000021DDDE5D1E0>)]Comparisons:a < b :testing __gt__(1, 2)testing __eq__(1, 2)result of a < b: Truea <= b:testing __gt__(1, 2)result of a <= b: Truea == b:testing __eq__(1, 2)result of a == b: Falsea >= b:testing __gt__(1, 2)testing __eq__(1, 2)result of a >= b: Falsea > b :testing __gt__(1, 2)result of a > b: False

虽然该装饰器能很容易的创建完全有序类型,但衍生出的比较函数执行的可能会更慢,以及产生更复杂的堆栈跟踪。如果性能基准测试表明这是程序的瓶颈,则实现所有六个富比较函数可能会提高速度。

排序规则

在Python3中已经废弃了旧时的比较(cmp)函数,因此例如 sorted(),min(),max()等方法不在支持 cmp参数, 但仍然支持key函数。functools提供了 cmp_to_key() 用于将cmp函数转换成key函数。

例如给定一个正整数列表,输出用这些正整数能够拼接成的最大整数。如果是Python2的程序可以是这样:

L = [97, 13, 4, 246]def my_cmp(a, b):""" 将比较的两个数字拼接成整数, 比较数值大小"""return int(str(b) + str(a)) - int(str(a) + str(b))L.sort(cmp=my_cmp)
print(''.join(map(str, L)))# 输出 97424613

但Python3的 sort 函数已废弃 cmp 参数,可以使用 cmp_to_key 将cmp函数转换成key函数:

from functools import cmp_to_keyL = [97, 13, 4, 246]def my_cmp(a, b):""" 将比较的两个数字拼接成整数, 比较数值大小"""return int(str(b) + str(a)) - int(str(a) + str(b))L.sort(key=cmp_to_key(my_cmp))
print(''.join(map(str, L)))# 输出 97424613

cmp 函数接收两个参数,比较它们,如果小于返回负数,相等返回0,大于返回正数。 key 函数接收一个参数,返回用于排序的键。

缓存

lru_cache() 装饰器是 缓存淘汰算法(最近最少使用)的一种实现。其使用函数的参数作为key结果作为value缓存在hash结构中(因此函数的参数必须是hashable),如果后续使用相同参数再次调用将从hash从返回结果。同时装饰器还添加了检查缓存转态方法(cache_info())和清空缓存方法(cache_clear())给函数。

import functools@functools.lru_cache()
def demo(a):print('called demo with {}'.format(a))return a ^ 2MAX = 2print('初次调用:')
for i in range(MAX):demo(i)
print(demo.cache_info())print('\n第二次调用:')
for i in range(MAX + 1):demo(i)
print(demo.cache_info())print('\n清空缓存后:')
demo.cache_clear()
print(demo.cache_info())print('\n再次调用:')
for i in range(MAX):demo(i)
print(demo.cache_info())

代码中多次调用 demo() 方法。首次调用后结果存在缓存中。cache_info() 返回一个命名元组,包括 hits,misses,maxsizecurrsize 。当第二次调用时命中缓存的调用将直接返回缓存内容,cache_clear() 用于清空当前缓存。

初次调用:
called demo with 0
called demo with 1
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)第二次调用:
called demo with 2
CacheInfo(hits=2, misses=3, maxsize=128, currsize=3)清空缓存后:
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)再次调用:
called demo with 0
called demo with 1
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)

为了防止缓存在长时间运行的流程中无限制地增长,特别设置了 maxsize 参数, 默认是128,设置为None时,则禁用LRU功能,缓存可以无限增长。同时还提供了 typed 参数,用于设置是否区别参数类型,默认为Fals。如果设置为True,那么类似如 demo(1)demo(1.0) 将被视为不同的值不同的调用。

Reduce方法

Python3中取消了全局命名空间中的 reduce() 函数,将 reduced() 放到了 functools 模块中,要使用 reduce() 的话,要先从 functools 中加载。

from functools import reduceprint(reduce(lambda a, b: a + b, range(11)))# 计算1加到10 结果 55

函数重载

在动态类型的语言(如Python)中,如果需要根据参数的类型执行不同的操作,简单直接的方法就是检查参数的类型。但在行为差异明显的情况下需要分离成单独的函数。 functools 提供 singledispatch() 装饰器注册一组通用函数基于函数的第一个参数的类型自动切换,类似于强类型语言中的函数重载。

import functools@functools.singledispatch
def myfunc(arg):print('default myfunc({!r})'.format(arg))@myfunc.register(int)
def myfunc_int(arg):print('myfunc_int({})'.format(arg))@myfunc.register(list)
def myfunc_list(arg):print('myfunc_list({})'.format(' '.join(arg)))myfunc('string argument')
myfunc(1)
myfunc(2.3)
myfunc(['a', 'b', 'c'])

singledispatch() 装饰的函数是默认实现, 使用其 register() 属性装饰接收其他类型参数的函数。调用时会根据 register() 中注册的类型自动选择实现函数。没有则使用默认实现。

default myfunc('string argument')
myfunc_int(1)
default myfunc(2.3)
myfunc_list(a b c)

另外再有继承的情况下,当类型没有精确匹配时,将根据继承顺序,选择最接近的类型。

import functoolsclass A:passclass B(A):passclass C(A):passclass D(B):passclass E(C, D):pass@functools.singledispatch
def myfunc(arg):print('default myfunc({})'.format(arg.__class__.__name__))@myfunc.register(A)
def myfunc_A(arg):print('myfunc_A({})'.format(arg.__class__.__name__))@myfunc.register(B)
def myfunc_B(arg):print('myfunc_B({})'.format(arg.__class__.__name__))@myfunc.register(C)
def myfunc_C(arg):print('myfunc_C({})'.format(arg.__class__.__name__))myfunc(A())
myfunc(B())
myfunc(C())
myfunc(D())
myfunc(E())
myfunc_A(A)
myfunc_B(B)
myfunc_C(C)
myfunc_B(D)
myfunc_C(E)

在上面代码中,类D和E没有与任何已注册的泛型函数匹配,所以根据其类的继承顺序进行选择。

Python标准库笔记(9) — functools模块

转载于:https://www.cnblogs.com/jhao/p/9023849.html

Python标准库笔记(9) — functools模块相关推荐

  1. 细数python标准库中低调的模块

    有没有遇到过这种情况,在网络上搜索如何使用Python进行某种操作,最终找到一个第三方库,直到后来发现标准库中包含的模块或多或少都可以满足你的需求.这种情况并不罕见, 整理了一些python标准库中鲜 ...

  2. Python标准库datetime之date模块详解

    Python标准库datetime之date模块详解 datetime是Python提供的操作日期和时间的标准库,主要有datetime.date模块.datetime.time模块及datetime ...

  3. Python标准库datetime之datetime模块详解

    Python标准库datetime之datetime模块详解 1.日期时间对象 日期时间对象是指具有日期(年月日)和时间(时分秒)双重属性的实例 日期时间对象的类型为datetime.datetime ...

  4. Python标准库中的os模块

     Python的标准库中的os模块包含普遍的操作系统功能.如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的.即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux ...

  5. python import re_Python标准库笔记(2) — re模块

    re模块提供了一系列功能强大的正则表达式(regular expression)工具,它们允许你快速检查给定字符串是否与给定的模式匹配(match函数), 或者包含这个模式(search函数).正则表 ...

  6. Python标准库中的pickle模块

     pickle  -  Python对象序列化. pickle模块实现了用于序列化和反序列化Python对象结构的二进制协议."pickle"是将Python对象层次结构转换为 ...

  7. Python标准库中的marshal模块

     marshal-内部的Python对象序列化 该模块包含可以以二进制格式读取和写入Python值的函数.该格式是针对Python的,但独立于机器架构问题(例如,您可以将Python值写入PC上的 ...

  8. python string模块template_Python标准库笔记(1) — string模块

    String模块包含大量实用常量和类,以及一些过时的遗留功能,并还可用作字符串操作. 1. 常用方法 常用方法描述str.capitalize()把字符串的首字母大写str.center(width) ...

  9. python pprint_Python标准库笔记(8) — pprint模块

    目录[-] pprint -- 更美观的打印数据结构 pprint 模块包含一个"美观打印器(PrettyPrinter)",用于产生美观的数据结构视图.格式化程序生成可以由解释器 ...

最新文章

  1. linux系统 插优盘安装xvidcap,linux下的视频录制软件xvidcap
  2. Google Objective-C Style Guide
  3. 构建更好的敏捷项目管理组织所需的4种工具
  4. mybatis foreach 错误_MyBatis高级结果映射之一对一映射
  5. 腾讯Android自动化测试实战3.1.4 Robotium的控件获取、操作及断言
  6. RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)
  7. airdrop 是 蓝牙吗_您可以在Windows PC或Android手机上使用AirDrop吗?
  8. 重新解读DDD领域驱动设计(一)
  9. 具有Stormpath和Spring Boot的OAuth 2.0令牌管理
  10. linux 怎么把^M去掉
  11. CFileDialog
  12. 软件测试nodejs面试题,nodejs单元测试和性能测试
  13. 转载--Github优秀java项目集合(中文版) - 涉及java所有的知识体系
  14. pyqsplitter 保持一个窗口不能拖动_Axure教程:左侧导航如何自适应浏览器窗口高度?...
  15. 中国指数基金与ETF价格战简史(1)
  16. IDEA 创建类注释模板
  17. 腾讯联手联通推出车联网“网卡”,打“内容”+“流量”的组合拳...
  18. RapidMiner Studio入门
  19. MarkDown在线生成简历
  20. 蜂鸟E203开源RISC-V开发板:蜂鸟FPGA开发板和JTAG调试器介绍

热门文章

  1. 信贷产品额度定价场景下的回归模型效果评估
  2. PTA-拼题A打卡奖励
  3. 算法训练营 重编码_编码训练营手册:沉浸式工程程序介绍
  4. 联想G50-30出厂win8换win7心得体会,解决鼠标,键盘失灵等问题
  5. 用C语言求排列组合数
  6. RION——一种快速、紧凑、通用的数据格式
  7. 地下城与勇士(DNF)海上列车副本(列车上的海贼、夺回西部线、幽灵列车、 雾都赫伊斯、阿登高地、特快列车追击战、卡勒特指挥部)(童年的回忆)
  8. python3使用pdfminer读取pdf文件
  9. android的wifi直连,WLAN 直连  |  Android 开源项目  |  Android Open Source Project
  10. 社科 |《今日简史》:尤瓦尔·赫拉利最新力作,“简史三部曲”最终篇