11. 標準ライブラリをひとめぐり --- その II
******************************************

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


11.1. 出力のフォーマット
========================

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

   >>> import reprlib
   >>> reprlib.repr(set('supercalifragilisticexpialidocious'))
   "{'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" モジュールは、文化によって異なるデータ形式のデータベースにア
クセスします。 "locale" の "format_string()" 関数で "grouping" 引数を
使うと、桁区切り文字を入れて数値をフォーマットできます。

   >>> import locale
   >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
   'English_United States.1252'
   >>> conv = locale.localeconv()          # 表記規則のマッピングを取得する
   >>> x = 1234567.8
   >>> locale.format_string("%d", x, grouping=True)
   '1,234,567'
   >>> locale.format_string("%s%.*f", (conv['currency_symbol'],
   ...                      conv['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 = 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('{0} --> {1}'.format(filename, newname))

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

テンプレートのもう一つの用途は、複数の出力形式の細かな違いからプログラ
ムのロジックを分離することです。 これにより、XML ファイル、プレーンテ
キストのレポート、HTML のウェブレポートに合わせたテンプレートを差し替
えて使えます。


11.3. バイナリデータレコードのレイアウトの操作
==============================================

"struct" モジュールでは、様々な長さのバイナリレコード形式を操作する
"pack()" や "unpack()" といった関数を提供しています。 以下の例では、
"zipfile" モジュールを使わずに、ZIP ファイルのヘッダ情報を巡回する方法
を示しています。""H""  と ""I"" というパック符号は、それぞれ2バイトと4
バイトの符号無し 整数を表しています。 ""<"" は、そのパック符号が
standard サイズであり、バイトオーダーがリトルエンディアンであることを
示しています:

   import struct

   with open('myfile.zip', 'rb') as f:
       data = f.read()

   start = 0
   for i in range(3):                      # 最初の 3 つのファイルヘッダを表示する
       start += 14
       fields = struct.unpack('<IIIHH', 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     # 次のヘッダまで進む


11.4. マルチスレッディング
==========================

スレッド処理 (threading) は、実行順序に依存関係のない複数のタスクを切
り離す技術です。 他のタスクをバックグラウンドで実行しながらユーザー入
力を受け付けるアプリケーションでは、スレッドを使って応答性を高めること
ができます。 関連する用途として、別のスレッドで行う計算処理と並行して
I/O を実行することも挙げられます。

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

   import threading, zipfile

   class AsyncZip(threading.Thread):
       def __init__(self, infile, outfile):
           super().__init__()
           self.infile = infile
           self.outfile = outfile

       def run(self):
           with zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) as f:
               f.write(self.infile)
           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()    # バックグラウンドのタスクが終わるまで待つ
   print('Main program waited until background was done.')

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

こうしたツールは強力ですが、小さな設計上の誤りが再現の難しい問題につな
がることがあります。 そのため、タスク間の調整では、あるリソースへのア
クセスをすべて 1 つのスレッドに集め、"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

デフォルトでは、 "info()" と "debug()" による出力は抑制され、出力は標
準エラーに送信されます。選択可能な送信先には、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)                   # 参照を作成する
   >>> d = weakref.WeakValueDictionary()
   >>> d['primary'] = a            # 参照を作成しない
   >>> d['primary']                # オブジェクトがまだ存在すれば取得する
   10
   >>> del a                       # 唯一の参照を削除する
   >>> gc.collect()                # 直ちにガベージコレクションを実行する
   0
   >>> d['primary']                # 項目は自動的に削除された
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       d['primary']                # 項目は自動的に削除された
     File "C:/python315/lib/weakref.py", line 46, in __getitem__
       o = self.data[key]()
   KeyError: 'primary'


11.7. リストを操作するツール
============================

データ構造が必要な場面の多くは、組み込みのリスト型で対応できます。 た
だし、性能上のトレードオフが異なる別の実装が必要になる場合もあります。

"array" (配列) モジュールでは、"array" オブジェクトを提供しています。
配列はリストに似ていますが、同種のデータのみを よりコンパクトに格納し
ます。  以下の例に示すのは、通常 1 要素あたり 16 バイト使う Python の
int オブジェクトのリストではなく、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" オブジェクトを提供しています。
リスト型に似ていますが、データの追加と左端からの取り出しが速く、その一
方で中間にある値の参照は遅くなります。こうしたオブジェクトはキューや木
構造の幅優先探索の実装に向いています:

   >>> 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)                      # リストをヒープの順序に並べ替える
   >>> heappush(data, -5)                 # 新しい項目を追加する
   >>> [heappop(data) for i in range(3)]  # 小さい方から 3 つの項目を取り出す
   [-5, 0, 1]


11.8. 10 進浮動小数点演算
=========================

"decimal" モジュールでは、 10 進浮動小数の算術演算をサポートする
"Decimal" データ型を提供しています。組み込みの 2 進浮動小数の実装であ
る "float" に比べて、このクラスがとりわけ便利なのは、以下の場合です

* 財務アプリケーションやその他の正確な10進表記が必要なアプリケーション
  、

* 精度の制御、

* 法的または規制上の理由に基づく値丸めの制御、

* 有効桁数の追跡が必要になる場合

* ユーザが手計算の結果と同じ演算結果を期待するようなアプリケーション。

例えば、70 セントの電話代に 5% の税金を加える計算では、10 進浮動小数点
と 2 進浮動小数点で結果が異なります。 結果を最も近いセント単位に丸める
と、その違いがはっきり現れます。

   >>> from decimal import *
   >>> round(Decimal('0.70') * Decimal('1.05'), 2)
   Decimal('0.74')
   >>> round(.70 * 1.05, 2)
   0.73

"Decimal" の計算結果は末尾のゼロを保持します。 掛け合わせる数がどちら
も小数点以下 2 桁であることから、結果を小数点以下 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
   >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
   False

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

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