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

ツアーの第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()" 関数の 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_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のwebレポート用のテンプレートに、同じプログラムロジック
から値を埋め込むことができます。


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):                      # show the first 3 file headers
       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     # 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

デフォルトでは、"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)                   # 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 "<stdin>", line 1, in <module>
       d['primary']                # entry was automatically removed
     File "C:/python38/lib/weakref.py", line 46, in __getitem__
       o = self.data[key]()
   KeyError: 'primary'


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

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

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

   >>> 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 *
   >>> 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
   >>> sum([0.1]*10) == 1.0
   False

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

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