functools------管理函数的工具
作用:处理其他函数的函数。
functools模块提供了一些工具来调整或扩展函数和其他可回调对象,而不必完全重写。
1.修饰符
functools模块提供的主要工具是partial类,它可以用来“包装”一个有默认参数的可回调对象。得到的对象本身是可回调的,可以看作就像是原来的函数。它与原函数的参数完全回调,调用时也可以提供额外的位置或命名参数。可以使用partial而不是lambda为函数,有些参数可以不指定。
partial对象
下面这个例子显示了函数myfunc()的两个简单的partial对象。show_details()的输出包含这个部分对象的func,args和keywords的属性。
import functools
def myfunc(a, b=2):
print('called myfunc with:', (a, b))
return
def show_details(name, f, is_partial=False):
print('%s:' % 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 balues 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:')
获取函数属性
默认情况下,partial对象没有__name__或__doc__属性。如果没有这些属性,写实的函数将更难调试。使用updata_wrapper()可以从原函数将属性复制或添加到partial对象。
def myfunc(a, b=2): print('called myfunc with:', (a, b)) return def show_details(name, f): '''Show details of a callable object.''' print('%s:' % name) print('object:', f) print('__name__') try: print(f.__name__) except: print('(no __name__)') print('__doc__', repr(f.__doc__)) print('**********************************') return 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) functools.update_wrapper(p1, myfunc) show_details('updated wrapper', p1)
添加到包装器的属性在WRAPPER_ASSIGNMENTS中定义,而WRAPPER_UPDATES列出了要修改的值。
myfunc: object: <function myfunc at 0x00000000022E9B88> __name__ myfunc __doc__ None ********************************** raw wrapper: object: functools.partial(<function myfunc at 0x00000000022E9B88>, 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 0x00000000022E9B88>, b=4) __name__ myfunc __doc__ None **********************************
其他可回调对象
Partial适用于任何可回调对象,而不只是单独的函数。
class MyClass(): ''' Demonstration class for functools''' def method1(self,a,b=2): '''Docstring for method1()''' print('called method1 with:',(self,a,b)) def method2(self,c,d=5): '''Docstirng for method2''' print('called method2 with:',(self,c,d)) wrapped_method2 = functools.partial(method2,'wrapped c') functools.update_wrapper(wrapped_method2,method2) 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('%s:' % name) print('object:',f) print(__name__) try: print(f.__name__) except AttributeError: print('no __name__') print('__doc__',repr(f.__doc__)) o = MyClass() show_details('method1 straight', o.method1) o.method1('no default for a', b=3) print('*********************11111111***********************') p1 = functools.partial(o.method1,b=4) functools.update_wrapper(p1,o.method1) show_details('method1 wrapper',p1) p1('a goes here') print('*********************22222222*********************') show_details('method2', o.method2) o.method2('no default for c',d=6) print('*********************33333333*********************') show_details('wrapped method2', o.wrapped_method2) o.wrapped_method2('no default for c',d=6) print('*********************44444444*********************')
这个例子由一个实例以及实例的方法来创建部分对象。
method1 straight: object: <bound method MyClass.method1 of <__main__.MyClass object at 0x0000000003E199C8>> __main__ method1 __doc__ 'Docstring for method1()' called method1 with: (<__main__.MyClass object at 0x0000000003E199C8>, 'no default for a', 3) *********************11111111*********************** method1 wrapper: object: functools.partial(<bound method MyClass.method1 of <__main__.MyClass object at 0x0000000003E199C8>>, b=4) __main__ method1 __doc__ 'Docstring for method1()' called method1 with: (<__main__.MyClass object at 0x0000000003E199C8>, 'a goes here', 4) *********************22222222********************* method2: object: <bound method MyClass.method2 of <__main__.MyClass object at 0x0000000003E199C8>> __main__ method2 __doc__ 'Docstirng for method2' called method2 with: (<__main__.MyClass object at 0x0000000003E199C8>, 'no default for c', 6) *********************33333333********************* wrapped method2: object: functools.partial(<function MyClass.method2 at 0x0000000002556318>, 'wrapped c') __main__ method2 __doc__ 'Docstirng for method2' called method2 with: ('wrapped c', 'no default for c', 6) *********************44444444*********************
为修饰符获取函数属性
在修饰符中使用时,更新包赚的可回调对象的属性尤其有用,因为变换后的函数最后会得到原‘裸’函数的属性。
def show_details(name,f): '''Show details of a callable object.''' print('%s:' % name) print('object:',f) print('__name__:') try: print(f.__name__) except AttributeError: print('(no __name__)') print('__doc__', repr(f.__doc__)) print('****************************') return def simple_decorator(f): @functools.wraps(f) def decorated(a='decorated defaults', b=1): print('decorated:',(a,b)) print('******************************') f(a,b=b) return return decorated def myfunc(a,b=2): 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()。
myfunc: object: <function myfunc at 0x00000000025CB438> __name__: myfunc __doc__ None **************************** myfunc: ('unwrapped,default b', 2) myfunc: ('unwrapped,passing b', 3) ***************************** wrapped_myfunc: object: <function myfunc at 0x00000000025CBC18> __name__: myfunc __doc__ None **************************** decorated: ('decorated defaults', 1) ****************************** myfunc: ('decorated defaults', 1) decorated: ('args to wrapped', 4) ****************************** myfunc: ('args to wrapped', 4) ****************************** decorated_myfunc: object: <function decorated_myfunc at 0x00000000123FD0D8> __name__: decorated_myfunc __doc__ None **************************** decorated: ('decorated defaults', 1) ****************************** myfunc: ('decorated defaults', 1) decorated: ('args to decorated', 4) ****************************** myfunc: ('args to decorated', 4)
2.比较
在python2中,类可以定义一个__cmp__()方法,后者会根据这个对象小于,等于还是大于所比较的元素返回-1,0或1.python2.1引入了富比较(rich comparsion)方法API(__lt__(),__le__(),__eq__(),__ne__(),__gt__()和__ge__()),可以完成一个比较操作并返回一个Boolean值。python3则废弃了__cmp__()而代之以这些新方法。所以functools提供了一些工具,以便于编写python2类而且同时符合python3中新的比较需求。
富比较
设计富比较API是为了支持涉及复杂比较的类,从而以最高效的方式实现各个测试。不过,对于比较相对简单的类,手动地创建各个富比较方法就没有必要了。total_ordering()类修饰符取一个提供了部分方法的类,并添加其余的方法。
import inspect from pprint import pprint @functools.total_ordering class MyObject(object): def __init__(self, val): self.val = val def __eq__(self, other): print('testing __eq__(%s, %s)' % (self.val, other.val)) return self.val == other.val def __gt__(self, other): print('testing __gt__(%s, %s)' % (self.val, other.val)) return self.val > other.val print('Methods:\n') pprint(inspect.getmembers(MyObject, inspect.ismethod)) a = MyObject(1) b = MyObject(2) print('\nComparisons:') for expr in ['a < b', 'a <= b', 'a == b', 'a >=b', 'a > b']: print('\n%-6s:' % expr) result = eval(expr) print('result of %s: %s' % (expr, result))
这个类必须提供__eq__()和另一个富比较方法的实现。修饰符会增加其余方法的实现,它们利用类提供的比较来完成工作。
Methods: [] Comparisons: a < b : testing __gt__(1, 2) testing __eq__(1, 2) result of a < b: True a <= b: testing __gt__(1, 2) result of a <= b: True a == b: testing __eq__(1, 2) result of a == b: False a >=b : testing __gt__(1, 2) testing __eq__(1, 2) result of a >=b: False a > b : testing __gt__(1, 2) result of a > b: False
注:在python中,eval()方法是一个经常用到的函数,我们在编写输入函数的时候,需要把input()函数写进eval()方法中,这样得到的输入结果就不会是字符串类型的了。
比对序
由于python3中废弃了老式比较函数,因此sort()之类的函数中也不再支持cmp参数。对于使用了比较函数的python2程序,可以用cmp_to_key()将它们转换为一个返回比对键(collation key)的函数,这个键用于确定元素在最终序列中的位置。