Python3标准库之functools管理函数的工具详解

1. functools管理函数的工具

functools模块提供了一些工具来调整或扩展函数和其他callable对象,从而不必完全重写。

1.1 修饰符

functools模块提供的主要工具就是partial类,可以用来“包装”一个有默认参数的callable对象。得到的对象本身就是callable,可以把它看作是原来的函数。它与原函数的参数完全相同,调用时还可以提供额外的位置或命名函数。可以使用partial而不是lambda为函数提供默认参数,有些参数可以不指定。

1.1.1 部分对象

第一个例子显示了函数myfunc()的两个简单partial对象。show_details()的输出中包含这个部分对象(partial object)的func、args和keywords属性。

import functools
def myfunc(a, b=2):
 "Docstring for myfunc()."
 print(' called myfunc with:', (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)
 return
show_details('myfunc', myfunc)
myfunc('a', 3)
print()
# Set a different default value for 'b', but require
# the caller to provide 'a'.
p1 = functools.partial(myfunc, b=4)
show_details('partial with named default', p1, True)
p1('passing a')
p1('override b', b=5)
print()
# Set default values for both 'a' and 'b'.
p2 = functools.partial(myfunc, 'default a', b=99)
show_details('partial with defaults', p2, True)
p2()
p2(b='override b')
print()
print('Insufficient arguments:')
p1()

在这个例子的最后,调用了之前创建的第一个partial,但没有为a传入一个值,这便会导致一个异常。

1.1.2 获取函数属性

默认的,partial对象没有__name__或__doc__属性。如果没有这些属性,被修饰的函数将更难调试。使用update_wrapper()可以从原函数将属性复制或增加到partial对象。

import functools
def myfunc(a, b=2):
 "Docstring for myfunc()."
 print(' called myfunc with:', (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中定义,另外WARPPER_UPDATES列出了要修改的值。

1.1.3 其他callable

partial适用于任何callable对象,而不只是独立的函数。

import functools
class 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__))
 return
o = 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()

这个例子从一个包含__call__()方法的类实例中创建部分对象。

1.1.4 方法和函数

partial()返回一个可以直接使用的callable,partialmethod()返回的callable则可以用作对象的非绑定方法。在下面的例子中,这个独立函数两次被增加为MyClass的属性,一次使用partialmethod()作为method1(),另一次使用partial()作为method2()。

import functools
def standalone(self, a=1, b=2):
 "Standalone function"
 print(' called standalone with:', (self, a, b))
 if self is not None:
  print(' self.attr =', self.attr)
class MyClass:
 "Demonstration class for 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。

1.1.5 获取修饰符的函数属性

更新所包装callable的属性对修饰符尤其有用,因为转换后的函数最后会得到原“裸”函数的属性。

import functools
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()
def simple_decorator(f):
 @functools.wraps(f)
 def decorated(a='decorated defaults', b=1):
  print(' decorated:', (a, b))
  print(' ', end=' ')
  return f(a, b=b)
 return decorated
def myfunc(a, b=2):
 "myfunc() is not complicated"
 print(' myfunc:', (a, b))
 return

# The raw function
show_details('myfunc', myfunc)
myfunc('unwrapped, default b')
myfunc('unwrapped, passing b', 3)
print()
# Wrap explicitly
wrapped_myfunc = simple_decorator(myfunc)
show_details('wrapped_myfunc', wrapped_myfunc)
wrapped_myfunc()
wrapped_myfunc('args to wrapped', 4)
print()
# Wrap with decorator syntax
@simple_decorator
def decorated_myfunc(a, b):
 myfunc(a, b)
 return
show_details('decorated_myfunc', decorated_myfunc)
decorated_myfunc()
decorated_myfunc('args to decorated', 4)

functools提供了一个修饰符wraps(),它会对所修饰的函数应用update_wrapper()。

1.2 比较

在Python 2中,类可以定义一个__cmp__()方法,它会根据这个对象小于、对于或者大于所比较的元素而分别返回-1、0或1.Python 2.1引入了富比较(rich comparison)方法API(__lt__()、__le__()、__eq__()、__ne__()、__gt__()和__ge__()) ,可以完成一个比较操作并返回一个布尔值。Python 3废弃了__cmp__()而代之以这些新的方法,另外functools提供了一些工具,从而能更容易地编写符合新要求的类,即符合Python 3中新的比较需求。

1.2.1 富比较

设计富比较API是为了支持涉及复杂比较的类,以最高效的方式实现各个测试。不过,如果比较相对简单的类,就没有必要手动地创建各个富比价方法了。total_ordering()类修饰符可以为一个提供了部分方法的类增加其余的方法。

import functools
import inspect
from pprint import pprint
@functools.total_ordering
class MyObject:
 def __init__(self, val):
  self.val = val
 def __eq__(self, other):
  print(' testing __eq__({}, {})'.format(
   self.val, other.val))
  return self.val == other.val
 def __gt__(self, other):
  print(' testing __gt__({}, {})'.format(
   self.val, other.val))
  return self.val > other.val
print('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))

这个类必须提供__eq__()和另外一个富比较方法的实现。这个修饰符会增加其余方法的实现,它们会使用所提供的比较。如果无法完成一个比较,这个方法应当返回NotImplemented,从而在另一个对象上使用逆比较操作符尝试比较,如果仍无法比较,便会完全失败。

1.2.2 比对序

由于Python 3废弃了老式的比较函数,sort()之类的函数中也不再支持cmp参数。对于使用了比较函数的较老的程序,可以用cmp_to_key()将比较函数转换为一个返回比较键(collation key)的函数,这个键用于确定元素在最终序列中的位置。

import functools
class MyObject:
 def __init__(self, val):
  self.val = val
 def __str__(self):
  return 'MyObject({})'.format(self.val)
def compare_obj(a, b):
 """Old-style comparison function.
 """
 print('comparing {} and {}'.format(a, b))
 if a.val < b.val:
  return -1
 elif a.val > b.val:
  return 1
 return 0
# Make a key function using cmp_to_key()
get_key = functools.cmp_to_key(compare_obj)
def get_key_wrapper(o):
 "Wrapper function for get_key to allow for print statements."
 new_key = get_key(o)
 print('key_wrapper({}) -> {!r}'.format(o, new_key))
 return new_key
objs = [MyObject(x) for x in range(5, 0, -1)]
for o in sorted(objs, key=get_key_wrapper):
 print(o)

正常情况下,可以直接使用cmp_to_key(),不过这个例子中引入了一个额外的包装器函数,这样调用键函数时可以打印更多的信息。

如输出所示,sorted()首先对序列中的每一个元素调用get_key_wrapper()以生成一个键。cmp_to_key()返回的键是functools中定义的一个类的实例,这个类使用传入的老式比较函数实现富比较API。所有键都创建之后,通过比较这些键来对序列排序。

1.3 缓存

lru_cache()修饰符将一个函数包装在一个“最近最少使用的”缓存中。函数的参数用来建立一个散列键,然后映射到结果。后续的调用如果有相同的参数,就会从这个缓存获取值而不会再次调用函数。这个修饰符还会为函数增加方法来检查缓存的状态(cache_info())和清空缓存(cache_clear())。

import functools
@functools.lru_cache()
def expensive(a, b):
 print('expensive({}, {})'.format(a, b))
 return a * b
MAX = 2

print('First set of calls:')
for i in range(MAX):
 for j in range(MAX):
  expensive(i, j)
print(expensive.cache_info())
print('\nSecond set of calls:')
for i in range(MAX + 1):
 for j in range(MAX + 1):
  expensive(i, j)
print(expensive.cache_info())
print('\nClearing cache:')
expensive.cache_clear()
print(expensive.cache_info())
print('\nThird set of calls:')
for i in range(MAX):
 for j in range(MAX):
  expensive(i, j)
print(expensive.cache_info())

这个例子在一组嵌套循环中执行了多个expensive()调用。第二次调用时有相同的参数值,结果在缓存中。清空缓存并再次运行循环时,这些值必须重新计算。

为了避免一个长时间运行的进程导致缓存无限制的扩张,要指定一个最大大小。默认为128个元素,不过对于每个缓存可以用maxsize参数改变这个大小。

import functools
@functools.lru_cache(maxsize=2)
def expensive(a, b):
 print('called expensive({}, {})'.format(a, b))
 return a * b
def make_call(a, b):
 print('({}, {})'.format(a, b), end=' ')
 pre_hits = expensive.cache_info().hits
 expensive(a, b)
 post_hits = expensive.cache_info().hits
 if post_hits > pre_hits:
  print('cache hit')
print('Establish the cache')
make_call(1, 2)
make_call(2, 3)
print('\nUse cached items')
make_call(1, 2)
make_call(2, 3)
print('\nCompute a new value, triggering cache expiration')
make_call(3, 4)
print('\nCache still contains one old item')
make_call(2, 3)
print('\nOldest item needs to be recomputed')
make_call(1, 2)

在这个例子中,缓存大小设置为2个元素。使用第3组不同的参数(3,4)时,缓存中最老的元素会被清除,代之以这个新结果。

lru_cache()管理的缓存中键必须是可散列的,所以对于用缓存查找包装的函数,它的所有参数都必须是可散列的。

import functools
@functools.lru_cache(maxsize=2)
def expensive(a, b):
 print('called expensive({}, {})'.format(a, b))
 return a * b
def make_call(a, b):
 print('({}, {})'.format(a, b), end=' ')
 pre_hits = expensive.cache_info().hits
 expensive(a, b)
 post_hits = expensive.cache_info().hits
 if post_hits > pre_hits:
  print('cache hit')
make_call(1, 2)
try:
 make_call([1], 2)
except TypeError as err:
 print('ERROR: {}'.format(err))
try:
 make_call(1, {'2': 'two'})
except TypeError as err:
 print('ERROR: {}'.format(err))

如果将一个不能散列的对象传入这个函数,则会产生一个TypeError。

1.4 缩减数据集

reduce()函数取一个callable和一个数据序列作为输入。它会用这个序列中的值调用这个callable,并累加得到的输出来生成单个值作为输出。

import functools
def do_reduce(a, b):
 print('do_reduce({}, {})'.format(a, b))
 return a + b
data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data)
print('result: {}'.format(result))

这个例子会累加序列中的数。

可选的initializer参数放在序列最前面,像其他元素一样处理。可以利用这个参数以新输入更新前面计算的值。

import functools
def do_reduce(a, b):
 print('do_reduce({}, {})'.format(a, b))
 return a + b
data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data, 99)
print('result: {}'.format(result))

在这个例子中,使用前面的总和99来初始化reduce()计算的值。

如果没有initializer参数,那么只有一个元素的序列会自动缩减为这个值。空列表会生成一个错误,除非提供一个initializer参数。

import functools
def do_reduce(a, b):
 print('do_reduce({}, {})'.format(a, b))
 return a + b
print('Single item in sequence:',
  functools.reduce(do_reduce, [1]))
print('Single item in sequence with initializer:',
  functools.reduce(do_reduce, [1], 99))
print('Empty sequence with initializer:',
  functools.reduce(do_reduce, [], 99))
try:
 print('Empty sequence:', functools.reduce(do_reduce, []))
except TypeError as err:
 print('ERROR: {}'.format(err))

由于initializer参数相当于一个默认值,但也要与新值结合(如果输入序列不为空),所以必须仔细考虑这个参数的使用是否适当,这很重要。如果默认值与新值结合没有意义,那么最好是捕获TypeError而不是传入一个initializer参数。

1.5 泛型函数

在类似Python的动态类型语言中,通常需要基于参数的类型完成稍有不同的操作,特别是在处理元素列表与单个元素的差别时。直接检查参数的类型固然很简单,但是有些情况下,行为差异可能被隔离到单个的函数中,对于这些情况,functools提供了singledispatch()修饰符来注册一组泛型函数(generic function),可以根据函数第一个参数类型自动切换。

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()')
 for item in arg:
  print(' {}'.format(item))
myfunc('string argument')
myfunc(1)
myfunc(2.3)
myfunc(['a', 'b', 'c'])

新函数的register()属性相当于另一个修饰符,用于注册替代实现。用singledispatch()包装的第一个函数是默认实现,在未指定其他类型特定函数时就使用这个默认实现,在这个例子中特定类型就是float。

没有找到这个类型的完全匹配时,会计算继承顺序,并使用最接近的匹配类型。

import functools
class A:
 pass
class B(A):
 pass
class C(A):
 pass
class D(B):
 pass
class 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())

在这个例子中,类D和E与已注册的任何泛型函数都不完全匹配,所选择的函数取决于类层次结构。

总结

到此这篇关于Python3标准库之functools管理函数的工具详解的文章就介绍到这了,更多相关Python3 functools管理函数工具内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python使用标准库根据进程名如何获取进程的pid详解

    前言 标准库是Python的一个组成部分.这些标准库是Python为你准备好的利器,可以让编程事半功倍.特别是有时候需要获取进程的pid,但又无法使用第三方库的时候.下面话不多说了,来一起看看详细的介绍吧. 方法适用linux平台. 方法1 使用subprocess 的check_output函数执行pidof命令 from subprocess import check_output def get_pid(name): return map(int,check_output(["pidof&

  • Python functools模块学习总结

    文档 地址 functools.partial 作用: functools.partial 通过包装手法,允许我们 "重新定义" 函数签名 用一些默认参数包装一个可调用对象,返回结果是可调用对象,并且可以像原始对象一样对待 冻结部分函数位置函数或关键字参数,简化函数,更少更灵活的函数参数调用 复制代码 代码如下: #args/keywords 调用partial时参数 def partial(func, *args, **keywords):     def newfunc(*farg

  • Python使用functools模块中的partial函数生成偏函数

    python 中提供一种用于对函数固定属性的函数(与数学上的偏函数不一样) # 通常会返回10进制 int('12345') # print 12345 # 使用参数 返回 8进制 int('11111', 8) # print 4681 每次都得添加参数比较麻烦, functools提供了partial的方法 import functools foo = functools.partial(int, base=8) foo('11111') # print 4681 通过这种方法生成一个固定参

  • Python标准库shutil用法实例详解

    本文实例讲述了Python标准库shutil用法.分享给大家供大家参考,具体如下: shutil模块提供了许多关于文件和文件集合的高级操作,特别提供了支持文件复制和删除的功能. 文件夹与文件操作 copyfileobj(fsrc, fdst, length=16*1024): 将fsrc文件内容复制至fdst文件,length为fsrc每次读取的长度,用做缓冲区大小 fsrc: 源文件 fdst: 复制至fdst文件 length: 缓冲区大小,即fsrc每次读取的长度 import shuti

  • 详解Python中的装饰器、闭包和functools的教程

    装饰器(Decorators) 装饰器是这样一种设计模式:如果一个类希望添加其他类的一些功能,而不希望通过继承或是直接修改源代码实现,那么可以使用装饰器模式.简单来说Python中的装饰器就是指某些函数或其他可调用对象,以函数或类作为可选输入参数,然后返回函数或类的形式.通过这个在Python2.6版本中被新加入的特性可以用来实现装饰器设计模式. 顺便提一句,在继续阅读之前,如果你对Python中的闭包(Closure)概念不清楚,请查看本文结尾后的附录,如果没有闭包的相关概念,很难恰当的理解P

  • Python中functools模块函数解析

    Python自带的 functools 模块提供了一些常用的高阶函数,也就是用于处理其它函数的特殊函数.换言之,就是能使用该模块对可调用对象进行处理. functools模块函数概览 functools.cmp_to_key(func) functools.total_ordering(cls) functools.reduce(function, iterable[, initializer]) functools.partial(func[, args][, *keywords]) func

  • Python3标准库之functools管理函数的工具详解

    1. functools管理函数的工具 functools模块提供了一些工具来调整或扩展函数和其他callable对象,从而不必完全重写. 1.1 修饰符 functools模块提供的主要工具就是partial类,可以用来"包装"一个有默认参数的callable对象.得到的对象本身就是callable,可以把它看作是原来的函数.它与原函数的参数完全相同,调用时还可以提供额外的位置或命名函数.可以使用partial而不是lambda为函数提供默认参数,有些参数可以不指定. 1.1.1 部

  • Python标准库datetime之datetime模块用法分析详解

    目录 1.日期时间对象 2.创建日期时间对象 2.1.通过datetime.datetime.utcnow()创建 2.2.通过datetime.datetime.today()函数创建 2.3.通过datetime.datetime.now()创建 2.4.通过datetime.datetime()创建 2.5.查看创建的对象 2.6.查看datetime可以处理的最大的日期时间对象及最小的日期时间对象 3.日期事件对象的属性 4.日期时间对象转换为时间元组 5.将日期时间对象转化为公元历开始

  • 用python标准库difflib比较两份文件的异同详解

    [需求背景] 有时候我们要对比两份配置文件是不是一样,或者比较两个文本是否异样,可以使用linux命令行工具diff a_file b_file,但是输出的结果读起来不是很友好.这时候使用python的标准库difflib就能满足我们的需求. 下面这个脚本使用了difflib和argparse,argparse用于解析我们给此脚本传入的两个参数(即两份待比较的文件),由difflib执行比较,比较的结果放到了一个html里面,只要找个浏览器打开此html文件,就能直观地看到比较结果,两份文件有差

  • 对Python3之进程池与回调函数的实例详解

    进程池 代码演示 方式一 from multiprocessing import Pool def deal_task(n): n -= 1 return n if __name__ == '__main__': n = 10 p = Pool(4) for i in range(4): res = p.apply(deal_task, args=(n,)) #调用apply是一个串行的效果,任务会被进程一个一个的处理,直接得到结果 #前提是执行的任务必须要有返回值 print(res) 方式二

  • python3 requests库文件上传与下载实现详解

    在接口测试学习过程中,遇到了利用requests库进行文件下载和上传的问题.同样,在真正的测试过程中,我们不可避免的会遇到上传和下载的测试. 文件上传: url = ztx.host+'upload/uploadFile?CSRFToken='+self.getCSRFToken()#上传文件的接口地址 header = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko', '

  • Python标准库json模块和pickle模块使用详解

    将Python数据类型转换为其他代码格式叫做(序列化),而json就是在各个代码实现转换的中间件. 序列化要求: 1. 只能有int,str,bool,list,dict,tuple的类型支持序列化. 2. json序列化是以字符串形式出现.那么:lis= "[11,22,33]" 这样的也能称为序列化. 3. 必须将数据类型包裹在list或dict内进行转换. 4. json内部的str格式,必须以双引号来进行包裹. 5. bool值转换为小写的首字母 json.dumps 将py转

  • Python3标准库之threading进程中管理并发操作方法

    1. threading进程中管理并发操作 threading模块提供了管理多个线程执行的API,允许程序在同一个进程空间并发的运行多个操作. 1.1 Thread对象 要使用Thread,最简单的方法就是用一个目标函数实例化一个Thread对象,并调用start()让它开始工作. import threading def worker(): """thread worker function""" print('Worker') threads

  • Python3标准库总结

    Python3标准库 操作系统接口 os模块提供了不少与操作系统相关联的函数. >>> import os >>> os.getcwd() # 返回当前的工作目录 'C:\\Python34' >>> os.chdir('/server/accesslogs') # 修改当前的工作目录 >>> os.system('mkdir today') # 执行系统命令 mkdir 0 建议使用 "import os" 风格

  • Python3标准库之dbm UNIX键-值数据库问题

    1. dbm UNIX键-值数据库 dbm是面向DBM数据库的一个前端,DBM数据库使用简单的字符串值作为键来访问包含字符串的记录.dbm使用whichdb()标识数据库,然后用适当的模块打开这些数据库.dbm还被用作shelve的一个后端,shelve使用pickle将对象存储在一个DBM数据库中. 1.1 数据库类型 Python提供了很多模块来访问DBM数据库.具体选择的默认实现取决于当前系统上可用的库以及编译Python时使用的选项.特定实现有单独的接口,这使得Python程序可以与用其

  • Python3标准库glob文件名模式匹配的问题

    1. glob文件名模式匹配 尽管glob API很小,但这个模块的功能却很强大.只要程序需要查找文件系统中名字与某个模式匹配的一组文件,就可以使用这个模块.要创建一个文件名列表,要求其中各个文件名都有某个特定的扩展名.前缀或者中间都有某个共同的字符串,就可以使用glob而不用编写定制代码来扫描目录内容. glob的模式规则与re模块使用的正则表达式并不相同.实际上,glob的模式遵循标准UNIX路径扩展规则.只使用几个特殊字符来实现两个不同的通配符和字符区间.模式规则应用于文件名中的段(在路径

随机推荐