11. 標準ライブラリミニツアー - その 2

もう一つのツアーでは、プロフェッショナルプログラミングを支える もっと高度なモジュールをカバーします。ここで挙げるモジュールは、 小さなスクリプトの開発ではほとんど使いません。


11.1 出力のフォーマット

repr モジュールでは、 大きなコンテナや、深くネストしたコンテナを省略して表示するバージョンの repr() を提供しています:

    >>> import repr   
    >>> repr.repr(set('supercalifragilisticexpialidocious'))
    "set(['a', 'c', 'd', 'e', 'f', 'g', ...])"

pprint モジュールを使うと、 組み込み型やユーザ定義型がより洗練された形式で出力されるよう制御できます。 出力が複数行にわたる場合には、``pretty printer'' が改行を追加して、 入れ子構造を理解しやすいようにインデントを挿入します:

    >>> import pprint
    >>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
    ...     'yellow'], 'blue']]]
    ...
    >>> pprint.pprint(t, width=30)
    [[[['black', 'cyan'],
       'white',
       ['green', 'red']],
      [['magenta', 'yellow'],
       'blue']]]

textwrap モジュールは、 一段落の文を指定したスクリーン幅にぴったり収まるように調整します:

    >>> import textwrap
    >>> doc = """The wrap() method is just like fill() except that it returns
    ... a list of strings instead of one big string with newlines to separate
    ... the wrapped lines."""
    ...
    >>> print textwrap.fill(doc, width=40)
    The wrap() method is just like fill()
    except that it returns a list of strings
    instead of one big string with newlines
    to separate the wrapped lines.

locale モジュールは、文化ごと に特化したデータ表現形式のデータベースにアクセスします。 localeformat 関数の grouping 属性を使えば、数値の各桁を適切な 区切り文字でグループ化してフォーマットできます:

    >>> import locale
    >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
    'English_United States.1252'
    >>> conv = locale.localeconv()          # get a mapping of conventions
    >>> x = 1234567.8
    >>> locale.format("%d", x, grouping=True)
    '1,234,567'
    >>> locale.format("%s%.*f", (conv['currency_symbol'],
    ...	      conv['int_frac_digits'], x), grouping=True)
    '$1,234,567.80'


11.2 文字列テンプレート

string モジュールには、 柔軟で、エンドユーザが簡単に編集できる簡単な構文を備えたTemplate クラスが入っています。このクラスを使うと、ユーザがアプリケーションの出力 をカスタマイズしたいときに全てを置き換えなくてもすみます。

テンプレートでは、"$" と有効な Python 識別子名 (英数字と アンダースコア) からなるプレースホルダ名を使います。プレースホルダの 周りを丸括弧で囲えば、間にスペースをはさまなくても後ろに英数文字を 続けられます。"$$" のようにすると、"$" 自体をエスケープ できます:

>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'

substitute メソッドは、プレースホルダに相当する値が辞書や キーワード引数にない場合に KeyError を送出します。 メールマージ型アプリケーションの場合、ユーザが入力するデータは不完全 なことがあるので、欠落したデータがあるとプレースホルダをそのままにして 出力する safe_substitute メソッドを使う方が適切でしょう:

>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
  . . .
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'

Template をサブクラス化すると、区切り文字を自作できます。例えば、 画像ブラウザ用にバッチで名前を変更するユーティリティを作っていたとして、 現在の日付や画像のシーケンス番号、ファイル形式といったプレースホルダに パーセント記号を選んだとします:

>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
>>> fmt = raw_input('Enter rename style (%d-date %n-seqnum %f-format):  ')
Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f

>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
...     base, ext = os.path.splitext(filename)
...     newname = t.substitute(d=date, n=i, f=ext)
...     print '%s --> %s' % (filename, newname)

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

テンプレートのもう一つの用途は、複数ある出力様式からのプログラムロジック の分離です。テンプレートを使えば、カスタムのテンプレートを XML ファイル 用や平文テキストのレポート、 HTML で書かれた web レポート用などに 置き換えられます。


11.3 バイナリデータレコードの操作

struct モジュールでは、 可変長のバイナリレコード形式を操作するpack()unpack() といった関数を提供しています。以下の例では、 ZIP ファイルのヘッダ情報にわたってループする方法を示しています (2バイトと 4 バイトの符号無し整数を表すパックコード "H""H" を使っています):

    import struct

    data = open('myfile.zip', 'rb').read()
    start = 0
    for i in range(3):                      # show the first 3 file headers
        start += 14
        fields = struct.unpack('LLLHH', data[start:start+16])
        crc32, comp_size, uncomp_size, filenamesize, extra_size =  fields

        start += 16
        filename = data[start:start+filenamesize]
        start += filenamesize
        extra = data[start:start+extra_size]
        print filename, hex(crc32), comp_size, uncomp_size

        start += extra_size + comp_size     # skip to the next header


11.4 マルチスレッド処理

スレッド処理 (threading) とは、順序的な依存関係にない複数のタスクを 分割するテクニックです。スレッド処理は、ユーザの入力を受け付けつつ、 背後で別のタスクを動かすようなアプリケーションの応答性を高めます。 主なユースケースには、 I/O を別のスレッドの計算処理と並列して 動作させるというものがあります。

以下のコードでは、高水準のモジュール threading で メインのプログラムを動かしながら背後で別のタスクを動作させられる ようにする方法を示しています:

    import threading, zipfile

    class AsyncZip(threading.Thread):
        def __init__(self, infile, outfile):
            threading.Thread.__init__(self)        
            self.infile = infile
            self.outfile = outfile
        def run(self):
            f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
            f.write(self.infile)
            f.close()
            print 'Finished background zip of: ', self.infile

    background = AsyncZip('mydata.txt', 'myarchive.zip')
    background.start()
    print 'The main program continues to run in foreground.'
    
    background.join()    # Wait for the background task to finish
    print 'Main program waited until background was done.'

マルチスレッドアプリケーションを作る上で最も難しい問題は、データやリソース を共有するスレッド間の調整 (coordination) です。この問題を解決するため、 threading モジュールではロックやイベント、状態変数、セマフォ といった数々の同期プリミティブを提供しています。

こうしたツールは強力な一方、ちょっとした設計上の欠陥で再現困難な 問題を引き起こすことがあります。したがって、タスク間調整では Queue モジュールを使って 他のスレッドから一つのスレッドにリクエストを送り込み、 一つのリソースへのアクセスをできるだけ一つのスレッドに集中させる アプローチを勧めます。スレッド間の通信や調整にQueue オブジェクトを使うと、設計が容易になり、可読性が高まり、信頼性が増します。


11.5 ログ記録

logging モジュールでは、 数多くの機能をそなえた柔軟性のあるログ記録システムを提供しています。 最も簡単な使い方では、ログメッセージをファイルや sys.stderr に送信します:

    import logging
    logging.debug('Debugging information')
    logging.info('Informational message')
    logging.warning('Warning:config file %s not found', 'server.conf')
    logging.error('Error occurred')
    logging.critical('Critical error -- shutting down')

上記のコードは以下のような出力になります:

    WARNING:root:Warning:config file server.conf not found
    ERROR:root:Error occurred
    CRITICAL:root:Critical error -- shutting down

デフォルトでは、単なる情報やデバッグメッセージの出力は抑制され、 出力は標準エラーに送信されます。選択可能な送信先には、email、データグラム、 ソケット、 HTTP サーバへの送信などがあります。新たにフィルタを作成 すると、DEBUG, INFO, WARNING, ERROR, CRITICAL といったメッセージのプライオリティに 従って配送先を変更できます。

ログ記録システムは Python から直接設定できますし、アプリケーションを 変更しなくてもカスタマイズできるよう、ユーザが編集できる設定ファイル でも設定できます。


11.6 弱参照

Python は自動的にメモリを管理します (ほとんどのオブジェクトの参照回数を カウントし、ガベージコレクションによって循環参照を除去します)。 オブジェクトに対する最後の参照がなくなってしばらくするとメモリは解放 されます。

このようなアプローチはほとんどのアプリケーションでうまく動作しますが、 中にはオブジェクトをどこか別の場所で利用するまでの間だけ追跡しておきたい 場合もあります。残念ながら、オブジェクトを追跡するだけでは、オブジェクトに 対する恒久的な参照を作ることになってしまいます。 weakref モジュールでは、 オブジェクトを参照を作らずに追跡するためのツールを提供しています。 弱参照オブジェクトが不要になると、弱参照 (weakref) テーブルから自動的に 除去され、コールバック関数がトリガされます。弱参照を使う典型的な 応用例には、作成コストの大きいオブジェクトのキャッシュがあります:

    >>> import weakref, gc
    >>> class A:
    ...     def __init__(self, value):
    ...             self.value = value
    ...     def __repr__(self):
    ...             return str(self.value)
    ...
    >>> a = A(10)                   # create a reference
    >>> d = weakref.WeakValueDictionary()
    >>> d['primary'] = a            # does not create a reference
    >>> d['primary']                # fetch the object if it is still alive
    10
    >>> del a                       # remove the one reference
    >>> gc.collect()                # run garbage collection right away
    0
    >>> d['primary']                # entry was automatically removed
    Traceback (most recent call last):
      File "<pyshell#108>", line 1, in -toplevel-
        d['primary']                # entry was automatically removed
      File "C:/PY24/lib/weakref.py", line 46, in __getitem__
        o = self.data[key]()
    KeyError: 'primary'


11.7 リスト操作のためのツール

多くのデータ構造は、組み込みリスト型を使った実装で事足ります。 とはいえ、時には組み込みリストとは違うパフォーマンス上のトレードオフを 持つような実装が必要になこともあります。

array モジュールでは、 同じ形式のデータだけしか保存できない代わりに、データをよりコンパクトに 保存できる、リスト型に似たarray() オブジェクトを提供しています。 以下の例では、通常要素あたり 16 バイトを必要とする Python 整数型の リストの代りに、 2 バイトの符号無しの 2 進数 (タイプコード "H") を使っている数値アレイを示します:

    >>> from array import array
    >>> a = array('H', [4000, 10, 700, 22222])
    >>> sum(a)
    26932
    >>> a[1:3]
    array('H', [10, 700])

collections モジュールでは、 リスト型に似た deque() オブジェクトを提供しています。 deque() オブジェクトでは、データの追加と左端からの取り出しが高速 な半面、中間にある値の検索が低速になります。こうしたオブジェクトは キューの実装や幅優先 (breadth first) のツリー探索に向いています:

    >>> from collections import deque
    >>> d = deque(["task1", "task2", "task3"])
    >>> d.append("task4")
    >>> print "Handling", d.popleft()
    Handling task1

    unsearched = deque([starting_node])
    def breadth_first_search(unsearched):
        node = unsearched.popleft()
        for m in gen_moves(node):
            if is_goal(m):
                return m
            unsearched.append(m)

リストのもう一つの実装の他に、このライブラリではソート済みのリストを 操作するための関数を備えたbisect のようなツールも提供しています:

    >>> import bisect
    >>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
    >>> bisect.insort(scores, (300, 'ruby'))
    >>> scores
    [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

heapq モジュールでは、 通常のリストでヒープを実装するための関数を提供しています。 ヒープでは、最も低い値をもつエントリがつねにゼロの位置に配置 されます。ヒープは、毎回リストをソートすることなく、最小の値をもつ 要素に繰り返しアクセスしるようなアプリケーションで便利です:

    >>> from heapq import heapify, heappop, heappush
    >>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
    >>> heapify(data)                      # rearrange the list into heap order
    >>> heappush(data, -5)                 # add a new entry
    >>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
    [-5, 0, 1]


11.8 10 進浮動小数演算

decimal では、 10 進浮動小数の 算術演算をサポートする Decimal データ型を提供しています。 組み込みの 2 進浮動小数の実装である float に比べて、この新たな クラスがとりわけ便利なのは、厳密な 10 進表記や計算精度の制御、法的または 規制上の理由に基づく値丸めの制御、有効桁数の追跡が必要になる金融計算など のアプリケーションや、ユーザが手計算の結果と同じ演算結果を期待するような アプリケーションの場合です。

例えば、 70 セントの電話代にかかる 5% の税金を計算しようとすると、 10 進の浮動小数点値と 2 進の浮動小数点値では違う結果になってしまいます。 例えば以下のような例では、計算結果を四捨五入してセント単位にしようと すると違いがはっきり現れます:

>>> from decimal import *       
>>> Decimal('0.70') * Decimal('1.05')
Decimal("0.7350")
>>> .70 * 1.05
0.73499999999999999

Decimal を使った計算では、末尾桁のゼロが保存されており、二つの 被演算子から自動的に有効数字を 4 桁と判断しています。Decimal は 手計算と同じ方法で計算を行い、 2 進浮動小数点が 10 進小数成分を正確に 表現できないことによって起きる問題を回避しています。

Decimal クラスは厳密な値を表現できるため、2 進浮動小数点数 では期待通りに計算できないようなモジュロの計算や等値テストも実現 できます:

>>> Decimal('1.00') % Decimal('.10')
Decimal("0.00")
>>> 1.00 % 0.10
0.09999999999999995
       
>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False

decimal モジュールを使うと、必要なだけの精度で算術演算を行えます:

>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal("0.142857142857142857142857142857142857")

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。