书籍摘录:Python标准库示例

作者:Doug Hellmann

第 3 章:算法

Python 包含了多个模块,用于以优雅和简洁的方式实现算法,并使用最适合任务的风格。它支持纯过程式、面向对象和函数式风格。这三种风格经常在同一程序的不同部分混合使用。

functools 包含了用于创建函数装饰器的函数,从而实现面向切面编程和超出传统面向对象方法支持的代码重用。它还提供了一个类装饰器,用于使用快捷方式实现所有丰富的比较 API,以及用于创建对其参数包含在内的函数的引用的 partial 对象。

itertools 模块包含了用于创建和使用函数式编程中使用的迭代器和生成器的函数。operator 模块通过为内置操作(例如算术或项目查找)提供基于函数的接口,从而消除了在使用函数式编程风格时对许多琐碎的 lambda 函数的需求。

contextlib 使资源管理对于所有编程风格都更轻松、更可靠和更简洁。将上下文管理器和 with 语句结合使用,减少了所需的 try:finally 块和缩进级别,同时确保文件、套接字、数据库事务和其他资源在正确的时间关闭和释放。

3.1 functools — 操作函数的工具

目的 操作其他函数的函数。

Python 版本 2.5 及更高版本

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

3.1.1 装饰器

functools 模块提供的主要工具是 partial 类,它可以用来“包装”具有默认参数的可调用对象。结果对象本身是可调用的,并且可以被视为它是原始函数。它接受与原始函数相同的所有参数,并且也可以使用额外的 positional 或命名参数来调用它。partial 可以代替 lambda 来为函数提供默认参数,同时保留一些未指定的参数。

Partial 对象

此示例显示了函数 myfunc() 的两个简单 partial 对象。show_details() 的输出包括 partial 对象的 func、args 和 keywords 属性。

import functools 

def myfunc(a, b=2): 
  """Docstring for myfunc().""" 
  print ’ called myfunc with:’, (a, b) 
  return 

def show_details(name, f, is_partial=False): 
    """Show details of a callable object.""" 
    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 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 的值,从而导致异常。

$ python functools_partial.py 

myfunc: object: <function myfunc at 0x100d9bf50> __name__: myfunc called myfunc with: (’a’, 3)

partial with named default: object: <functools.partial object at 0x100d993c0> func: <function myfunc at 0x100d9bf50> args: () keywords: {’b’: 4} called myfunc with: (’passing a’, 4) called myfunc with: (’override b’, 5)

partial with defaults: object: <functools.partial object at 0x100d99418> func: <function myfunc at 0x100d9bf50> args: (’default a’,) keywords: {’b’: 99} called myfunc with: (’default a’, 99) called myfunc with: (’default a’, ’override b’)

Insufficient arguments: Traceback (most recent call last): File "functools_partial.py", line 51, in <module> p1() TypeError: myfunc() takes at least 1 argument (1 given)

获取函数属性

默认情况下,partial 对象没有 __name__ 或 __doc__ 属性,并且没有这些属性,装饰函数更难调试。使用 update_wrapper() 将原始函数的属性复制或添加到 partial 对象。

import functools 

def myfunc(a, b=2): 
    """Docstring for myfunc()."""
    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 AttributeError: 
      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
print 

functools.update_wrapper(p1, myfunc) 
show_details(’updated wrapper’, p1) 

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

$ python functools_update_wrapper.py 

myfunc:
 object: <function myfunc at 0x100da2050>
 __name__: myfunc
 __doc__ ’Docstring for myfunc().’ 

raw wrapper:
 object: <functools.partial object at 0x100d993c0>
 __name__: (no __name__)
 __doc__ ’partial(func, *args, **keywords) -new function with parti 
al application\n of the given arguments and keywords.\n’ 

Updating wrapper:
 assign: (’__module__’, ’__name__’, ’__doc__’)
 update: (’__dict__’,) 

updated wrapper:
 object: <functools.partial object at 0x100d993c0>
 __name__: myfunc
 __doc__ ’Docstring for myfunc().’ 

其他可调用对象

Partials 可以与任何可调用对象一起使用,而不仅仅是独立的函数。

import functools 

class MyClass(object): 
   """Demonstration class for functools""" 

   def method1(self, a, b=2): 
      """Docstring for method1().""" 
      print ’ called method1 with:’, (self, a, b)
      return 

   def method2(self, c, d=5): 
      """Docstring for method2""" 
       print ’ called method2 with:’, (self, c, d)
       return 
       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)
        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 AttributeError: 
        print ’(no __name__)’
     print ’ __doc__’, repr(f.__doc__)
     return 

o = MyClass() 

show_details(’method1 straight’, o.method1)
o.method1(’no default for a’, b=3) 
print 

p1 = functools.partial(o.method1, b=4) 
functools.update_wrapper(p1, o.method1) 
show_details(’method1 wrapper’, p1) 
p1(a goes here’) 
print 

show_details(’method2’, o.method2)
o.method2(’no default for c’, d=6) 
print 

show_details(’wrapped method2’, o.wrapped_method2) 
o.wrapped_method2(’no default for c’, d=6) 
print 

show_details(’instance’, o) 
o(’no default for e’) 
print 
p2 = functools.partial(o, f=7) 
show_details(’instance wrapper’, p2) 
p2(e goes here’) 

此示例从实例和实例方法创建 partials。

$ python functools_method.py 

method1 straight: 
  object: <bound method MyClass.method1 of <__main__.MyClass object
at 0x100da3550>> 
  __name__: method1 
  __doc__ ’Docstring for method1().’ 
  called method1 with: (<__main__.MyClass object at 0x100da3550>, ’n 
o default for a’, 3) 

method1 wrapper: 
  object: <functools.partial object at 0x100d99470> 
  __name__: method1 
  __doc__ ’Docstring for method1().’ 
  called method1 with: (<__main__.MyClass object at 0x100da3550>, ’a 
 goes here’, 4) 

method2: 
  object: <bound method MyClass.method2 of <__main__.MyClass object
at 0x100da3550>> 
  __name__: method2 
  __doc__ ’Docstring for method2’ 
  called method2 with: (<__main__.MyClass object at 0x100da3550>, ’n 
o default for c’, 6) 

wrapped method2: 
  object: <functools.partial object at 0x100d993c0> 
  __name__: method2 
  __doc__ ’Docstring for method2’ 
  called method2 with: (’wrapped c’, ’no default for c’, 6) 

instance: 
  object: <__main__.MyClass object at 0x100da3550> 
  __name__: (no __name__) 
  __doc__ ’Demonstration class for functools’ 
  called object with: (<__main__.MyClass object at 0x100da3550>, ’no 
    default for e’, 6) 
  instance wrapper:
    object: <functools.partial object at 0x100d994c8>
    __name__: (no __name__) 
    __doc__ ’partial(func, *args, **keywords) -new function with part 
  ial application\n of the given arguments and keywords.\n’
    called object with: (<__main__.MyClass object at 0x100da3550>, ’e
  goes here’, 7) 

为装饰器获取函数属性

当在装饰器中使用时,更新包装的可调用对象的属性特别有用,因为转换后的函数最终具有原始“裸”函数的属性。

import functools 

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): 
     "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() 应用于装饰函数。

$ python functools_wraps.py 


myfunc:
 object: <function myfunc at 0x100da3488>
 __name__: myfunc
 __doc__ ’myfunc() is not complicated’ 

 myfunc: (’unwrapped, default b’, 2)
 myfunc: (’unwrapped, passing b’, 3) 

wrapped_myfunc:
 object: <function myfunc at 0x100da3500>
 __name__: myfunc
 __doc__ ’myfunc() is not complicated’ 

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 0x100da35f0>
 __name__: decorated_myfunc
 __doc__ None 

 decorated: (’decorated defaults’, 1)
    myfunc: (’decorated defaults’, 1)
 decorated: (’args to decorated’, 4)
    myfunc: (’args to decorated’, 4) 
3.1.2 比较

在 Python 2 中,类可以定义一个 __cmp__() 方法,该方法根据对象是小于、等于还是大于被比较的项目,返回 -1、0 或 1。Python 2.1 引入了比较方法 API(__lt__(), __le__(), __eq__(), __ne__(), __gt__() 和 __ge__()),它们执行单个比较操作并返回布尔值。Python 3 弃用了 __cmp__() 而支持这些新方法,因此 functools 提供了工具,使编写符合 Python 3 中新比较要求的 Python 2 类更容易。

富比较

富比较 API 旨在允许具有复杂比较的类以尽可能有效的方式实现每个测试。但是,对于比较相对简单的类,手动创建每个富比较方法没有意义。total_ordering() 类装饰器接受一个提供某些方法的类,并添加其余方法。

import functools 
import inspect 
from pprint import pprint 

@functools.total_ordering 
class MyObject(object):
    def __init__(self, val):
        self.val = val 
    def __eq__(self, other): 
        printtesting __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) 
      printresult of %s: <%s’ % (expr, result) 

该类必须提供 __eq__() 和另一个富比较方法的实现。装饰器添加其余方法的实现,这些方法通过使用提供的比较来工作。

$	python functools_total_ordering.py 

Methods: 


[(’__eq__’, <unbound method MyObject.__eq__>), 
 (’__ge__’, <unbound method MyObject.__ge__>), 
 (’__gt__’, <unbound method MyObject.__gt__>), 
 (’__init__’, <unbound method MyObject.__init__>), 
 (’__le__’, <unbound method MyObject.__le__>), 
 (’__lt__’, <unbound method MyObject.__lt__>)] 

Comparisons: 

a < b: 
 testing __gt__(2, 1) 
 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__(2, 1)
 result of a >= b: False 

a > b:
 testing __gt__(1, 2)
 result of a > b: False 

排序规则

由于旧式比较函数在 Python 3 中已弃用,因此对 sort() 等函数的 cmp 参数也不再受支持。使用比较函数的 Python 2 程序可以使用 cmp_to_key() 将它们转换为返回排序的函数,该键用于确定最终序列中的位置。

import functools
 
class MyObject(object):
   def __init__(self, val):
       self.val = val 
   def __str__(self): 
      return ’MyObject(%s)’ % self.val 

def compare_obj(a, b): 
    """Old-style comparison function.
    """ 
    print ’comparing %s and %s’ % (a, b) 
    return cmp(a.val, b.val) 

#	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(%s) -> %s’ % (o, new_key)
   return new_key 
objs = [ MyObject(x) for x in xrange(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。创建所有键后,序列将通过比较键进行排序。

$ python functools_cmp_to_key.py 

key_wrapper(MyObject(5)) -> <functools.K object at 0x100da2a50> 
key_wrapper(MyObject(4)) -> <functools.K object at 0x100da2a90> 
key_wrapper(MyObject(3)) -> <functools.K object at 0x100da2ad0> 
key_wrapper(MyObject(2)) -> <functools.K object at 0x100da2b10> 
key_wrapper(MyObject(1)) -> <functools.K object at 0x100da2b50> 
comparing MyObject(4) and MyObject(5) 
comparing MyObject(3) and MyObject(4) 
comparing MyObject(2) and MyObject(3) 
comparing MyObject(1) and MyObject(2) 
MyObject(1) 
MyObject(2) 
MyObject(3) 
MyObject(4) 
MyObject(5) 

另请参阅

functools (https://docs.pythonlang.cn/library/functools.html) 该模块的标准库文档。

富比较方法 (https://docs.pythonlang.cn/reference/datamodel.html# object.__lt__) 来自 Python 参考指南的富比较方法描述。

inspect (第 1200 页) 实时对象的内省 API。


© 版权所有 Pearson Education。保留所有权利。

此摘录来自 Doug Hellmann 编写的 ‘Python 标准库示例’ 一书,由 Pearson/Addison-Wesley Professional 出版,2011 年 6 月,ISBN 0321767349,版权 2011 Pearson Education, Inc. 欲了解更多信息,请访问 www.informit.com/title/0321767349

加载 Disqus 评论