5.5 管理上下文和with语句

    在Python中,在很多地方用到了上下文的管理。接下来会结合一些示例来对基本用法进行说明。

    上下文是通过with语句来定义的。以下的这个例子中使用了一个程序解析日志文件并保存为CSV格式。由于需要同时打开两个文件,因此需要使用嵌套的with语句来创建上下文。下例使用了复杂的正则表达式format_1_pat。接下来会进行说明。

    这个程序的实现代码如下。

    import gzip
    import csv
    with open("subset.csv", "w") as target:
      wtr= csv.writer( target )
      
    with gzip.open(path) as source:
        line_iter= (b.decode() for b in source)
        match_iter = (format_1_pat.match( line ) for line in
         line_iter)
        wtr.writerows( (m.groups() for m in match_iter if m is not
         None) )

    这里使用了两个上下文管理器。

    外部的上下文以with open("subset.csv", "w") as target为起始。使用Python中的open()函数打开一个文件,赋值给target变量以备后用。

    内部的上下文以with gzip.open(path, "r") as source为起始。gzip.open() 函数和open()函数的行为是类似的,也是打开一个文件赋值给一个上下文管理器。

    with语句结束,上下文也会相应终止并关闭引用的文件。即使当with上下文中有异常抛出,上下文管理器也会正常终止并相应关闭所引用的文件。

    总是使用with语句来操作文件 既然文件属于系统资源。当应用程序不再使用系统资源时,需要及时释放,这点是重要的。with语句可以确保资源被正确释放。

    为了完成上例,如下代码是用来将Apache HTTP服务器日志文件解析为通用日志格式(Common Log Format)的正则表达式。

    import re
    format_1_pat= re.compile(
      r"([\d.]+)\s+"   # digits and .'s: host
      r"(\S+)\s+"     # non-space: logname
      r"(\S+)\s+"     # non-space: user
      r"[(.+?)]\s+"   # Everything in []: time
      r'"(.+?)"\s+'    # Everything in "": request
      r"(\d+)\s+"     # digits: status
      r"(\S+)\s+"     # non-space: bytes
      r'"(.?)"\s+'    # Everything in "": referrer
      r'"(.
    ?)"\s*'    # Everything in "": user agent
    )

    以上表达式实现了不同日志格式中字段的解析,正是之前例子中使用的。

    5.5.1 使用小数上下文

    另一个经常使用的上下文的例子是小数上下文。它定义了很多decimal.Decimal计算的特性,包含了大量的规则用来求近似值或对值进行截取。

    考虑如下实现。

    import decimal
    PENNY=decimal.Decimal("0.00")

    price= decimal.Decimal('15.99')
    rate= decimal.Decimal('0.0075')
    print( "Tax=", (pricerate).quantize(PENNY), "Fully=", pricerate
    )

    with decimal.localcontext() as ctx:
      ctx.rounding= decimal.ROUND_DOWN
      tax= (price*rate).quantize(PENNY)
      print( "Tax=", tax )

    上例中演示了默认上下文和本地上下文。默认上下文有默认的求近似值的规则。而本地上下文演示了通过对特殊计算设置小数的近似规则来确保操作的一致性。

    with语句用来确保当本地上下文改变时原来的上下文可以复原。在这个上下文之外,应用了默认的求近似值规则。在上下文之内,应用自己特殊的近似值规则。

    5.5.2 其他上下文

    还有一些其他公共的上下文。几乎它们所有都涉及基本的输入/输出操作。大多数模块打开一个文件,创建一个上下文和一个类似文件的对象。

    锁和数据库的事务也会用到上下文。有时会需要释放一个外部锁,比如一个semaphore,再如希望数据库事务可以在执行成功时提交,抑或在失败时回滚。这些都已经在Python中的上下文中定义好了。

    PEP343文件提供了很多其他使用with语句和上下文管理器的例子,这些用法也许会在其他一些特殊场景中用到。

    我们也可能只是创建一些上下文管理器的类,或者创建含有多个用意的类,其中之一是上下文管理器。与file()对象是类似的。我们会看到很多上下文的设计方法。

    我们会在第8章“装饰器和mixin——横切方面”对这点进行回顾,会介绍多种方式来创建具有上下文管理功能的类。