Python 高级特性

这章有关Python中被认为高级的特性——就是说并非每一个语言都有的,也是说它们可能在更复杂的程序或库中更有用,但不是说特别特殊或特别复杂。html

强调这点很重要:这一章仅仅关于语言自身——关于辅之以Python的标准库功能的特殊语法所支持的特性,不包括那些智能的外部模块实现。python

在开发Python程序语言的过程当中,它的语法,独一无二。由于它很是透明。建议的更改经过不一样的角度评估并在公开邮件列表讨论,最终决定考虑到假设用例的重要性、添加更多特性的负担,其他语法的一致性、是否建议的变种易于读写和理解之间的平衡。这个过程由Python Enhancement Proposals(PEPs)的形式规范。最终这一章节中描述的特性在证实它们确实解决实际问题而且使用起来尽量简单后被添加。git

迭代器(Iterators), 生成表达式(generator expressions)和生成器(generators)

迭代器

简单github

重复工做是浪费,将不一样“土生土长”的方法替换为标准特性换来的是更加易于阅读和操做。web

Guido van Rossum — Adding Optional Static Typing to Python数据库

迭代器是依附于迭代协议的对象——基本意味它有一个next方法(method),当调用时,返回序列中的下一个项目。当无项目可返回时,引起(raise)StopIteration异常。express

迭代对象容许一次循环。它保留单次迭代的状态(位置),或从另外一个角度讲,每次循环序列都须要一个迭代对象。这意味咱们能够同时迭代同一个序列不仅一次。将迭代逻辑和序列分离使咱们有更多的迭代方式。编程

调用一个容器(container)的__iter__方法建立迭代对象是掌握迭代器最直接的方式。iter函数为咱们节约一些按键。缓存

>>> nums = [1,2,3]      # note that ... varies: these are different objects
>>> iter(nums)                           
<listiterator object at ...>
>>> nums.__iter__()                      
<listiterator object at ...>
>>> nums.__reversed__()                  
<listreverseiterator object at ...>

>>> it = iter(nums)
>>> next(it)            # next(obj) simply calls obj.next()
1
>>> it.next()
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

当在循环中使用时,StopIteration被接受并中止循环。但经过显式引起(invocation),咱们看到一旦迭代器元素被耗尽,存取它将引起异常。并发

使用for...in循环也使用__iter__方法。这容许咱们透明地开始对一个序列迭代。可是若是咱们已经有一个迭代器,咱们想在for循环中能一样地使用它们。为了实现这点,迭代器除了next还有一个方法__iter__来返回迭代器自身(self)。

Python中对迭代器的支持无处不在:标准库中的全部序列和无序容器都支持。这个概念也被拓展到其它东西:例如file对象支持行的迭代。

>>> f = open('/etc/fstab')
>>> f is f.__iter__()
True

file自身就是迭代器,它的__iter__方法并不建立一个单独的对象:仅仅单线程的顺序读取被容许。

生成表达式

第二种建立迭代对象的方式是经过 生成表达式(generator expression) ,列表推导(list comprehension)的基础。为了增长清晰度,生成表达式老是封装在括号或表达式中。若是使用圆括号,则建立了一个生成迭代器(generator iterator)。若是是方括号,这一过程被‘短路’咱们得到一个列表list

>>> (i for i in nums)                    
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]

在Python 2.7和 3.x中列表表达式语法被扩展到 字典和集合表达式。一个集合set当生成表达式是被大括号封装时被建立。一个字典dict在表达式包含key:value形式的键值对时被建立:

>>> {i for i in range(3)}   
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}   
{0: 0, 1: 1, 2: 4}

若是您不幸身陷古老的Python版本中,这个语法有点糟:

>>> set(i for i in 'abc')
set(['a', 'c', 'b'])
>>> dict((i, ord(i)) for i in 'abc')
{'a': 97, 'c': 99, 'b': 98}

生成表达式至关简单,不用多说。只有一个陷阱值得说起:在版本小于3的Python中索引变量(i)会泄漏。

生成器

生成器

生成器是产生一列结果而不是单一值的函数。

David Beazley — A Curious Course on Coroutines and Concurrency

第三种建立迭代对象的方式是调用生成器函数。一个 生成器(generator) 是包含关键字yield的函数。值得注意,仅仅是这个关键字的出现彻底改变了函数的本质:yield语句没必要引起(invoke),甚至没必要可接触。但让函数变成了生成器。当一个函数被调用时,其中的指令被执行。而当一个生成器被调用时,执行在其中第一条指令以前中止。生成器的调用建立依附于迭代协议的生成器对象。就像常规函数同样,容许并发和递归调用。
next被调用时,函数执行到第一个yield。每次遇到yield语句得到一个做为next返回的值,在yield语句执行后,函数的执行又被中止。

>>> def f():
...   yield 1
...   yield 2
>>> f()                                   
<generator object f at 0x...>
>>> gen = f()
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration

让咱们遍历单个生成器函数调用的整个历程。

>>> def f():
...   print("-- start --")
...   yield 3
...   print("-- middle --")
...   yield 4
...   print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)                            
-- finished --
Traceback (most recent call last):
 ...
StopIteration

相比常规函数中执行f()当即让print执行,gen不执行任何函数体中语句就被赋值。只有当gen.next()next调用,直到第一个yield部分的语句才被执行。第二个语句打印-- middle --并在遇到第二个yield时中止执行。第三个next打印-- finished --而且到函数末尾,由于没有yield,引起了异常。

当函数yield以后控制返回给调用者后发生了什么?每一个生成器的状态被存储在生成器对象中。从这点看生成器函数,好像它是运行在单独的线程,但这仅仅是假象:执行是严格单线程的,但解释器保留和存储在下一个值请求之间的状态。

为什么生成器有用?正如关于迭代器这部分强调的,生成器函数只是建立迭代对象的又一种方式。一切能被yield语句完成的东西也能被next方法完成。然而,使用函数让解释器魔力般地建立迭代器有优点。一个函数能够比须要next__iter__方法的类定义短不少。更重要的是,相比不得不对迭代对象在连续next调用之间传递的实例(instance)属性来讲,生成器的做者能更简单的理解局限在局部变量中的语句。

还有问题是为什么迭代器有用?当一个迭代器用来驱动循环,循环变得简单。迭代器代码初始化状态,决定是否循环结束,而且找到下一个被提取到不一样地方的值。这凸显了循环体——最值得关注的部分。除此以外,能够在其它地方重用迭代器代码。

双向通讯

每一个yield语句将一个值传递给调用者。这就是为什么PEP 255引入生成器(在Python2.2中实现)。可是相反方向的通讯也颇有用。一个明显的方式是一些外部(extern)语句,或者全局变量或共享可变对象。经过将先前无聊的yield语句变成表达式,直接通讯因PEP 342成为现实(在2.5中实现)。当生成器在yield语句以后恢复执行时,调用者能够对生成器对象调用一个方法,或者传递一个值 生成器,而后经过yield语句返回,或者经过一个不一样的方法向生成器注入异常。

第一个新方法是send(value),相似于next(),可是将value传递进做为yield表达式值的生成器中。事实上,g.next()g.send(None)是等效的。

第二个新方法是throw(type, value=None, traceback=None),等效于在yield语句处

raise type, value, traceback

不像raise(从执行点当即引起异常),throw()首先恢复生成器,而后仅仅引起异常。选用单次throw就是由于它意味着把异常放到其它位置,而且在其它语言中与异常有关。

当生成器中的异常被引起时发生什么?它能够或者显式引起,当执行某些语句时能够经过throw()方法注入到yield语句中。任一状况中,异常都以标准方式传播:它能够被exceptfinally捕获,或者形成生成器的停止并传递给调用者。

因完整性缘故,值得说起生成器迭代器也有close()方法,该方法被用来让本能够提供更多值的生成器当即停止。它用生成器的__del__方法销毁保留生成器状态的对象。

让咱们定义一个只打印出经过send和throw方法所传递东西的生成器。

>>> import itertools
>>> def g():
...     print '--start--'
...     for i in itertools.count():
...         print '--yielding %i--' % i
...         try:
...             ans = yield i
...         except GeneratorExit:
...             print '--closing--'
...             raise
...         except Exception as e:
...             print '--yield raised %r--' % e
...         else:
...             print '--yield returned %s--' % ans

>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--

注意: next仍是__next__?

在Python 2.x中,接受下一个值的迭代器方法是next,它经过全局函数next显式调用,意即它应该调用__next__。就像全局函数iter调用__iter__。这种不一致在Python 3.x中被修复,it.next变成了it.__next__。对于其它生成器方法——sendthrow状况更加复杂,由于它们不被解释器隐式调用。然而,有建议语法扩展让continue带一个将被传递给循环迭代器中send的参数。若是这个扩展被接受,可能gen.send会变成gen.__send__。最后一个生成器方法close显然被不正确的命名了,由于它已经被隐式调用。

链式生成器

注意: 这是PEP 380的预览(还未被实现,但已经被Python3.3接受)

好比说咱们正写一个生成器,咱们想要yield一个第二个生成器——一个子生成器(subgenerator)——生成的数。若是仅考虑产生(yield)的值,经过循环能够不费力的完成:

subgen = some_other_generator()
for v in subgen:
    yield v

然而,若是子生成器须要调用send()throw()close()和调用者适当交互的状况下,事情就复杂了。yield语句不得不经过相似于前一章节部分定义的try...except...finally结构来保证“调试”生成器函数。这种代码在PEP 380中提供,如今足够拿出将在Python 3.3中引入的新语法了:

yield from some_other_generator()

像上面的显式循环调用同样,重复从some_other_generator中产生值直到没有值能够产生,可是仍然向子生成器转发sendthrowclose

装饰器

总结

这个语言中使人激动的特性几乎充满歉意的,考虑到它可能没这么有用。

Bruce Eckel — An Introduction to Python Decorators

由于函数或类都是对象,它们也能被四处传递。它们又是可变对象,能够被更改。在函数或类对象建立后但绑定到名字前更改之的行为为装饰(decorator)。

“装饰器”后隐藏了两种意思——一是函数起了装饰做用,例如,执行真正的工做,另外一个是依附于装饰器语法的表达式,例如,at符号和装饰函数的名称。

函数能够经过函数装饰器语法装饰:

@decorator             # ②
def function():        # ①
    pass
  • 函数以标准方式定义。①
  • @作为定义为装饰器函数前缀的表达式②。在 @ 后的部分必须是简单的表达式,一般只是函数或类的名字。这一部分先求值,在下面的定义的函数准备好后,装饰器被新定义的函数对象做为单个参数调用。装饰器返回的值附着到被装饰的函数名。

装饰器能够应用到函数和类上。对类语义很明晰——类定义被看成参数来调用装饰器,不管返回什么都赋给被装饰的名字。

在装饰器语法实现前(PEP 318),经过将函数和类对象赋给临时变量而后显式调用装饰器而后将返回值赋给函数名,能够完成一样的事。这彷佛要打更多的字,也确实装饰器函数名用了两次同时临时变量要用至少三次,很容易出错。以上实例至关于:

def function():                  # ①
    pass
function = decorator(function)   # ②

装饰器能够堆栈(stacked)——应用的顺序是从底到上或从里到外。就是说最初的函数被看成第一次参数器的参数,不管返回什么都被做为第二个装饰器的参数……不管最后一个装饰器返回什么都被依附到最初函数的名下。

装饰器语法因其可读性被选择。由于装饰器在函数头部前被指定,显然不是函数体的一部分,它只能对整个函数起做用。以@为前缀的表达式又让它明显到不容忽视(根据PEP叫在您脸上……:))。当多个装饰器被应用时,每一个放在不一样的行很是易于阅读。

代替和调整原始对象

装饰器能够或者返回相同的函数或类对象或者返回彻底不一样的对象。第一种状况中,装饰器利用函数或类对象是可变的添加属性,例如向类添加文档字符串(docstring).装饰器甚至能够在不改变对象的状况下作有用的事,例如在全局注册表中注册装饰的类。在第二种状况中,简直无所不能:当什么不一样的东西取代了被装饰的类或函数,新对象能够彻底不一样。然而这不是装饰器的目的:它们意在改变装饰对象而非作不可预料的事。所以当一个函数在装饰时被彻底替代成不一样的函数时,新函数一般在一些准备工做后调用原始函数。一样,当一个类被装饰成一个新类时,新类一般源于被装饰类。当装饰器的目的是“每次都”作什么,像记录每次对被装饰函数的调用,只有第二类装饰器可用。另外一方面,若是第一类足够了,最好使用它由于更简单。

实现类和函数装饰器

对装饰器唯一的要求是它可以单参数调用。这意味着装饰器能够做为常规函数或带有__call__方法的类的实现,理论上,甚至lambda函数也行。

让咱们比较函数和类方法。装饰器表达式(@后部分)能够只是名字。只有名字的方法很好(打字少,看起来整洁等),可是只有当无需用参数定制装饰器时才可能。被写做函数的装饰器能够用如下两种方式:

>>> def simple_decorator(function):
...   print "doing decoration"
...   return function
>>> @simple_decorator
... def function():
...   print "inside function"
doing decoration
>>> function()
inside function

>>> def decorator_with_arguments(arg):
...   print "defining the decorator"
...   def _decorator(function):
...       # in this inner function, arg is available too
...       print "doing decoration,", arg
...       return function
...   return _decorator
>>> @decorator_with_arguments("abc")
... def function():
...   print "inside function"
defining the decorator
doing decoration, abc
>>> function()
inside function

这两个装饰器属于返回被装饰函数的类别。若是它们想返回新的函数,须要额外的嵌套,最糟的状况下,须要三层嵌套。

>>> def replacing_decorator_with_args(arg):
...   print "defining the decorator"
...   def _decorator(function):
...       # in this inner function, arg is available too
...       print "doing decoration,", arg
...       def _wrapper(*args, **kwargs):
...           print "inside wrapper,", args, kwargs
...           return function(*args, **kwargs)
...       return _wrapper
...   return _decorator
>>> @replacing_decorator_with_args("abc")
... def function(*args, **kwargs):
...     print "inside function,", args, kwargs
...     return 14
defining the decorator
doing decoration, abc
>>> function(11, 12)
inside wrapper, (11, 12) {}
inside function, (11, 12) {}
14

_wrapper函数被定义为接受全部位置和关键字参数。一般咱们不知道哪些参数被装饰函数会接受,因此wrapper将全部东西都创递给被装饰函数。一个不幸的结果就是显式参数很迷惑人。

相比定义为函数的装饰器,定义为类的复杂装饰器更简单。当对象被建立,__init__方法仅仅容许返回None,建立的对象类型不能更改。这意味着当装饰器被定义为类时,使用无参数的形式没什么意义:最终被装饰的对象只是装饰类的一个实例而已,被构建器(constructor)调用返回,并不很是有用。讨论在装饰表达式中给出参数的基于类的装饰器,__init__方法被用来构建装饰器。

>>> class decorator_class(object):
...   def __init__(self, arg):
...       # this method is called in the decorator expression
...       print "in decorator init,", arg
...       self.arg = arg
...   def __call__(self, function):
...       # this method is called to do the job
...       print "in decorator call,", self.arg
...       return function
>>> deco_instance = decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
...   print "in function,", args, kwargs
in decorator call, foo
>>> function()
in function, () {}

相对于正常规则(PEP 8)由类写成的装饰器表现得更像函数,所以它们的名字以小写字母开始。

事实上,建立一个仅返回被装饰函数的新类没什么意义。对象应该有状态,这种装饰器在装饰器返回新对象时更有用。

>>> class replacing_decorator_class(object):
...   def __init__(self, arg):
...       # this method is called in the decorator expression
...       print "in decorator init,", arg
...       self.arg = arg
...   def __call__(self, function):
...       # this method is called to do the job
...       print "in decorator call,", self.arg
...       self.function = function
...       return self._wrapper
...   def _wrapper(self, *args, **kwargs):
...       print "in the wrapper,", args, kwargs
...       return self.function(*args, **kwargs)
>>> deco_instance = replacing_decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
...   print "in function,", args, kwargs
in decorator call, foo
>>> function(11, 12)
in the wrapper, (11, 12) {}
in function, (11, 12) {}

像这样的装饰器能够作任何事,由于它能改变被装饰函数对象和参数,调用被装饰函数或不调用,最后改变返回值。

复制原始函数的文档字符串和其它属性

当新函数被返回代替装饰前的函数时,不幸的是原函数的函数名,文档字符串和参数列表都丢失了。这些属性能够部分经过设置__doc__(文档字符串),__module____name__(函数的全称)、__annotations__(Python 3中关于参数和返回值的额外信息)移植到新函数上,这些工做可经过functools.update_wrapper自动完成。

>>> import functools
>>> def better_replacing_decorator_with_args(arg):
...   print "defining the decorator"
...   def _decorator(function):
...       print "doing decoration,", arg
...       def _wrapper(*args, **kwargs):
...           print "inside wrapper,", args, kwargs
...           return function(*args, **kwargs)
...       return functools.update_wrapper(_wrapper, function)
...   return _decorator
>>> @better_replacing_decorator_with_args("abc")
... def function():
...     "extensive documentation"
...     print "inside function"
...     return 14
defining the decorator
doing decoration, abc
>>> function                           
<function function at 0x...>
>>> print function.__doc__
extensive documentation

一件重要的东西是从可迁移属性列表中所缺乏的:参数列表。参数的默认值能够经过__defaults____kwdefaults__属性更改,可是不幸的是参数列表自己不能被设置为属性。这意味着help(function)将显式无用的参数列表,使使用者迷惑不已。一个解决此问题有效可是丑陋的方式是使用eval动态建立wrapper。可使用外部external模块自动实现。它提供了对decorator装饰器的支持,该装饰器接受wrapper并将之转换成保留函数签名的装饰器。

综上,装饰器应该老是使用functools.update_wrapper或者其它方式赋值函数属性。

标准库中的示例

首先要说起的是标准库中有一些实用的装饰器,有三种装饰器:

  • classmethod让一个方法变成“类方法”,即它可以无需建立实例调用。当一个常规方法被调用时,解释器插入实例对象做为第一个参数self。当类方法被调用时,类自己被给作第一个参数,通常叫cls

    类方法也能经过类命名空间读取,因此它们没必要污染模块命名空间。类方法可用来提供替代的构建器(constructor):

    class Array(object):
        def __init__(self, data):
            self.data = data
    
        @classmethod
        def fromfile(cls, file):
            data = numpy.load(file)
            return cls(data)

    这比用一大堆标记的__init__简单多了。

  • staticmethod应用到方法上让它们“静态”,例如,原本一个常规函数,但经过类命名空间存取。这在函数仅在类中须要时有用(它的名字应该以_为前缀),或者当咱们想要用户觉得方法链接到类时也有用——虽然对实现自己没必要要。

  • property是对getter和setter问题Python风格的答案。经过property装饰的方法变成在属性存取时自动调用的getter。

    >>> class A(object):
    ...   @property
    ...   def a(self):
    ...     "an important attribute"
    ...     return "a value"
    >>> A.a                                   
    <property object at 0x...>
    >>> A().a
    'a value'

    例如A.a是只读属性,它已经有文档了:help(A)包含从getter方法获取的属性a的文档字符串。将a定义为property使它可以直接被计算,而且产生只读的反作用,由于没有定义任何setter。

    为了获得setter和getter,显然须要两个方法。从Python 2.6开始首选如下语法:

    class Rectangle(object):
        def __init__(self, edge):
            self.edge = edge
    
        @property
        def area(self):
            """Computed area.
    
            Setting this updates the edge length to the proper value.
            """
            return self.edge**2
    
        @area.setter
        def area(self, area):
            self.edge = area ** 0.5

    经过property装饰器取代带一个属性(property)对象的getter方法,以上代码起做用。这个对象反过来有三个可用于装饰器的方法gettersetterdeleter。它们的做用就是设定属性对象的getter、setter和deleter(被存储为fgetfsetfdel属性(attributes))。当建立对象时,getter能够像上例同样设定。当定义setter时,咱们已经在area中有property对象,能够经过setter方法向它添加setter,一切都在建立类时完成。

    以后,当类实例建立后,property对象和特殊。当解释器执行属性存取、赋值或删除时,其执行被下放给property对象的方法。

    为了让一切一清二楚[^5],让咱们定义一个“调试”例子:

    >>> class D(object):
    ...    @property
    ...    def a(self):
    ...      print "getting", 1
    ...      return 1
    ...    @a.setter
    ...    def a(self, value):
    ...      print "setting", value
    ...    @a.deleter
    ...    def a(self):
    ...      print "deleting"
    >>> D.a                                    
    <property object at 0x...>
    >>> D.a.fget                               
    <function a at 0x...>
    >>> D.a.fset                               
    <function a at 0x...>
    >>> D.a.fdel                               
    <function a at 0x...>
    >>> d = D()               # ... varies, this is not the same `a` function
    >>> d.a
    getting 1
    1
    >>> d.a = 2
    setting 2
    >>> del d.a
    deleting
    >>> d.a
    getting 1
    1

    属性(property)是对装饰器语法的一点扩展。使用装饰器的一大前提——命名不重复——被违反了,可是目前没什么更好的发明。为getter,setter和deleter方法使用相同的名字仍是个好的风格。

一些其它更新的例子包括:

  • functools.lru_cache记忆任意维持有限 参数:结果 对的缓存函数(Python
    3.2)
  • functools.total_ordering是一个基于单个比较方法而填充丢失的比较(ordering)方法(__lt__,__gt____le__等等)的类装饰器。

函数的废弃

好比说咱们想在第一次调用咱们不但愿被调用的函数时在标准错误打印一个废弃函数警告。若是咱们不想更改函数,咱们可用装饰器

class deprecated(object):
    """Print a deprecation warning once on first use of the function.

    >>> @deprecated()                    # doctest: +SKIP
    ... def f():
    ...     pass
    >>> f()                              # doctest: +SKIP
    f is deprecated
    """
    def __call__(self, func):
        self.func = func
        self.count = 0
        return self._wrapper
    def _wrapper(self, *args, **kwargs):
        self.count += 1
        if self.count == 1:
            print self.func.__name__, 'is deprecated'
        return self.func(*args, **kwargs)

也能够实现成函数:

def deprecated(func):
    """Print a deprecation warning once on first use of the function.

    >>> @deprecated                      # doctest: +SKIP
    ... def f():
    ...     pass
    >>> f()                              # doctest: +SKIP
    f is deprecated
    """
    count = [0]
    def wrapper(*args, **kwargs):
        count[0] += 1
        if count[0] == 1:
            print func.__name__, 'is deprecated'
        return func(*args, **kwargs)
    return wrapper

while-loop移除装饰器

例如咱们有个返回列表的函数,这个列表由循环建立。若是咱们不知道须要多少对象,实现这个的标准方法以下:

def find_answers():
    answers = []
    while True:
        ans = look_for_next_answer()
        if ans is None:
            break
        answers.append(ans)
    return answers

只要循环体很紧凑,这很好。一旦事情变得更复杂,正如真实的代码中发生的那样,这就很难读懂了。咱们能够经过yield语句简化它,但以后用户不得不显式调用嗯list(find_answers())

咱们能够建立一个为咱们构建列表的装饰器:

def vectorized(generator_func):
    def wrapper(*args, **kwargs):
        return list(generator_func(*args, **kwargs))
    return functools.update_wrapper(wrapper, generator_func)

而后函数变成这样:

@vectorized
def find_answers():
    while True:
        ans = look_for_next_answer()
        if ans is None:
            break
        yield ans

插件注册系统

这是一个仅仅把它放进全局注册表中而不更改类的类装饰器,它属于返回被装饰对象的装饰器。

class WordProcessor(object):
    PLUGINS = []
    def process(self, text):
        for plugin in self.PLUGINS:
            text = plugin().cleanup(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)

@WordProcessor.plugin
class CleanMdashesExtension(object):
    def cleanup(self, text):
        return text.replace('&mdash;', u'\N{em dash}')

这里咱们使用装饰器完成插件注册。咱们经过一个名词调用装饰器而不是一个动词,由于咱们用它来声明咱们的类是WordProcessor的一个插件。plugin方法仅仅将类添加进插件列表。

关于插件自身说下:它用真正的Unicode中的破折号符号替代HTML中的破折号。它利用unicode literal notation经过它在unicode数据库中的名称(“EM DASH”)插入一个符号。若是直接插入Unicode符号,将不可能区分所插入的和源程序中的破折号。

更多例子和参考

上下文管理器

上下文管理器是能够在with语句中使用,拥有__enter____exit__方法的对象。

with manager as var:
    do_something(var)

至关于如下状况的简化:

var = manager.__enter__()
try:
    do_something(var)
finally:
    manager.__exit__()

换言之,PEP 343中定义的上下文管理器协议容许将无聊的try...except...finally结构抽象到一个单独的类中,仅仅留下关注的do_something部分。

  1. __enter__方法首先被调用。它能够返回赋给var的值。as部分是可选的:若是它不出现,enter的返回值简单地被忽略。
  2. with语句下的代码被执行。就像try子句,它们或者成功执行到底,或者breakcontinuereturn,或者能够抛出异常。不管哪一种状况,该块结束后,__exit__方法被调用。若是抛出异常,异常信息被传递给__exit__,这将在下一章节讨论。一般状况下,异常可被忽略,就像在finally子句中同样,而且将在__exit__结束后从新抛出。

好比说咱们想确认一个文件在完成写操做以后被当即关闭:

>>> class closing(object):
...   def __init__(self, obj):
...     self.obj = obj
...   def __enter__(self):
...     return self.obj
...   def __exit__(self, *args):
...     self.obj.close()
>>> with closing(open('/tmp/file', 'w')) as f:
...   f.write('the contents\n')

这里咱们确保了当with块退出时调用了f.close()。由于关闭文件是很是常见的操做,该支持已经出如今file类之中。它有一个__exit__方法调用close,而且自己可做为上下文管理器。

>>> with open('/tmp/file', 'a') as f:
...   f.write('more contents\n')

try...finally常见的用法是释放资源。各类不一样的状况实现类似:在__enter__阶段资源被得到,在__exit__阶段释放,若是抛出异常也被传递。正如文件操做,每每这是对象使用后的天然操做,内置支持使之很方便。每个版本,Python都在更多的地方提供支持。

  • 全部相似文件的对象:

    • file ➔ 自动关闭
    • fileinput,tempfile(py >= 3.2)
    • bz2.BZ2Filegzip.GzipFile,
      tarfile.TarFile,zipfile.ZipFile
    • ftplib, nntplib ➔ 关闭链接(py >= 3.2)


    • multiprocessing.RLock ➔ 锁定和解锁
    • multiprocessing.Semaphore
    • memoryview ➔ 自动释放(py >= 3.2 或 3.3)
  • decimal.localcontext➔ 暂时更改计算精度
  • _winreg.PyHKEY ➔ 打开和关闭Hive Key
  • warnings.catch_warnings ➔ 暂时杀死(kill)警告
  • contextlib.closing ➔ 如上例,调用close
  • 并行编程

    • concurrent.futures.ThreadPoolExecutor
      并行调用而后杀掉线程池(py >= 3.2)
    • concurrent.futures.ProcessPoolExecutor
      并行调用并杀死进程池(py >= 3.2)
    • nogil ➔ 暂时解决GIL问题(仅仅cyphon :()

捕获异常

当一个异常在with块中抛出时,它做为参数传递给__exit__。三个参数被使用,和sys.exc_info()返回的相同:类型、值和回溯(traceback)。当没有异常抛出时,三个参数都是None。上下文管理器能够经过从__exit__返回一个真(True)值来“吞下”异常。例外能够轻易忽略,由于若是__exit__不使用return直接结束,返回None——一个假(False)值,以后在__exit__结束后从新抛出。

捕获异常的能力创造了有意思的可能性。一个来自单元测试的经典例子——咱们想确保一些代码抛出正确种类的异常:

class assert_raises(object):
    # based on pytest and unittest.TestCase
    def __init__(self, type):
        self.type = type
    def __enter__(self):
        pass
    def __exit__(self, type, value, traceback):
        if type is None:
            raise AssertionError('exception expected')
        if issubclass(type, self.type):
            return True # swallow the expected exception
        raise AssertionError('wrong exception type')

with assert_raises(KeyError):
    {}['foo']

使用生成器定义上下文管理器

当讨论生成器时,听说咱们相比实现为类的迭代器更倾向于生成器,由于它们更短小方便,状态被局部保存而非实例和变量中。另外一方面,正如双向通讯章节描述的那样,生成器和它的调用者之间的数据流能够是双向的。包括异常,能够直接传递给生成器。咱们想将上下文管理器实现为特殊的生成器函数。事实上,生成器协议被设计成支持这个用例。

@contextlib.contextmanager
def some_generator(<arguments>):
    <setup>
    try:
        yield <value>
    finally:
        <cleanup>

contextlib.contextmanager装饰一个生成器并转换为上下文管理器。生成器必须遵循一些被包装(wrapper)函数强制执行的法则——最重要的是它至少yield一次。yield以前的部分从__enter__执行,上下文管理器中的代码块当生成器停在yield时执行,剩下的在__exit__中执行。若是异常被抛出,解释器经过__exit__的参数将之传递给包装函数,包装函数因而在yield语句处抛出异常。经过使用生成器,上下文管理器变得更短小精炼。

让咱们用生成器重写closing的例子:

@contextlib.contextmanager
def closing(obj):
    try:
        yield obj
    finally:
        obj.close()

再把assert_raises改写成生成器:

@contextlib.contextmanager
def assert_raises(type):
    try:
        yield
    except type:
        return
    except Exception as value:
        raise AssertionError('wrong exception type')
    else:
        raise AssertionError('exception expected')

这里咱们用装饰器将生成函数转化为上下文管理器!


原文 Advanced Python Constructs
翻译 reverland