Python School 2.0.0 documentation

ユニコード文字列を考慮する

«  標準モジュール csv を使う   ::   Contents   ::   組込み関数 (zip / dict) を使う  »

ユニコード文字列を考慮する

Python で日本語を扱う場合は色々と大変でした。 数字やアルファベットだけの ASCII を扱う場合には問題になりませんが、日本語を扱う場合には大きな関門となります。 Python 2.x 系の場合は str 型と unicode 型を区別する必要がありました。 Python 3.0 では文字列は str 型、バイト列は bytes 型に変更されました。 そして Python 3.3 からは文字列は str 型に統一されました。内部的には Unicode で扱います。 Unicode と UTF-8 は違いますので、入力および出力のときにはエンコーディング指定を忘れないでください。 違いが分からない場合は調べておきましょう。

こうした諸々は非常に面倒であり、Python 初学者がつまづきやすい部分でもあります。 Python 3.4 では大きな苦労もなく日本語を扱えるようになっています。 特別な理由が無い限りは、これから書くプログラムは Python 3.4 以降をターゲットにすると良いでしょう。

なお、後方互換性を考える上では、複数の文字列リテラル表現方法が存在していたことに注意しましょう。 過去のソースコードやドキュメント、インターネット上の記事を読むと色々な注意書きがあるはずです。 Python の以前のバージョンでは文字列の型を明示するためにプリフィックスを付ける必要がありました。 unicode 型の場合は “u”、 bytes 型の場合は “b” を文字列リテラルの前に付けます。 詳細は Python 2 から Python 3 への移植 にまとまっていますので、バージョン間の差を知りたい場合にはよく読んでおきましょう。

日本語を含むデータの扱い方

csv-1.csv にヘッダー行を追加して csv-3.csv とします。

日付,始値,高値,安値,終値
2014-06-06,15138.75,15144.34,15042.59,15077.24
2014-06-05,15112.59,15141.14,15016.81,15079.37
2014-06-04,15067.41,15071.78,14985.21,15067.96
2014-06-03,15089.04,15091.49,15026.01,15034.25
2014-06-02,14777.51,14963.91,14777.51,14935.92

このデータを扱えるように csv-2.py を改修して csv-3.py とします。 引数に “–header-encoding” オプションを追加します。 このオプションが指定された場合、入力ファイル内にヘッダー行があり、そのエンコーディングは指定されたものとみなします。 オプション未指定の場合、ファイル内にヘッダー行が存在しないものとみなします。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Parse daily Tokyo stock prices.
"""

import argparse
import csv  # import standard "csv" module
import logging


def parse_args():
    """Parse arguments and set up logging verbosity.

    :rtype: parsed arguments as Namespace object.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file", dest="filename",
                        help="setting file", metavar="FILE")
    parser.add_argument("-o", "--output", dest="output",
                        help="output file", metavar="FILE")
    parser.add_argument("-n", "--dryrun", dest="dryrun",
                        help="dry run", default=False, action="store_true")
    parser.add_argument("-v", "--verbose", dest="verbose", default=False,
                        action="store_true", help="verbose mode")
    parser.add_argument("-q", "--quiet", dest="quiet", default=False,
                        action="store_true", help="quiet mode")
    # Add this line from boilerplate.
    parser.add_argument("--header-encoding", dest="header_encoding",
                        help="Encoding of header row")
    parser.add_argument("filename", nargs=1, help="CSV file path")

    args = parser.parse_args()

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)
    elif not args.quiet:
        logging.basicConfig(level=logging.INFO)

    return args


def process(args):
    """Parse daily Tokyo stock prices, and calculate up/down.
    If "--header-encoding" option is specified, first row is thought as header.
    Othewise, no header is included in the file.
    """
    encoding = args.header_encoding or 'utf-8'  # Default encoding is "utf-8"
    with open(args.filename[0], encoding=encoding) as fp:
        reader = csv.reader(fp)  # Instantiate CSV reader with file pointer.
        if args.header_encoding:
            header = next(reader)
        else:
            header = ("日付", "始値", "高値", "安値", "終値")
        for t in reader:
            # Assign each field on individual key/value pair in the dictionary.
            dt = {}
            for i, h in enumerate(header):
                dt[h] = t[i]
            # Calculate the differenciate of the day.
            diff = float(dt['終値']) - float(dt['始値'])
            if diff > 0:
                message = 'up'
            elif diff < 0:
                message = 'down'
            else:
                message = 'same'
            # Write out day, up/down/same, and diff.
            print('{}\t{:5}\t{}'.format(dt['日付'], message, round(diff, 2)))


def main():
    args = parse_args()
    process(args)


def test():
    pass

if __name__ == '__main__':
    main()

# vim: set et ts=4 sw=4 cindent fileencoding=utf-8 :

差分は以下の通りです。 変更点もちょっとづつ複雑になってきてしまいましたね。 ソースコードの中に日本語が埋め込まれており、あまり望ましいとは言えない状態です。 次の章では、組み込み関数を活用して、少し違う実装を試してみましょう。

@@ -26,6 +26,8 @@
     parser.add_argument("-q", "--quiet", dest="quiet", default=False,
                         action="store_true", help="quiet mode")
     # Add this line from boilerplate.
+    parser.add_argument("--header-encoding", dest="header_encoding",
+                        help="Encoding of header row")
     parser.add_argument("filename", nargs=1, help="CVS file path")

     args = parser.parse_args()
@@ -40,18 +42,23 @@

 def process(args):
     """Parse daily Tokyo stock prices, and calculate up/down.
+    If "--header-encoding" option is specified, first row is thought as header.
+    Othewise, no header is included in the file.
     """
-    with open(args.filename[0]) as fp:
+    encoding = args.header_encoding or 'utf-8'  # Default encoding is "utf-8"
+    with open(args.filename[0], encoding=encoding) as fp:
         reader = csv.reader(fp)  # Instantiate CSV reader with file pointer.
+        if args.header_encoding:
+            header = next(reader)
+        else:
+            header = ("日付", "始値", "高値", "安値", "終値")
         for t in reader:
-            # Assign each field on individual variables.
-            day = t[0]
-            price_begin = float(t[1])
-            price_max = float(t[2])
-            price_min = float(t[3])
-            price_end = float(t[4])
+            # Assign each field on individual key/value pair in the dictionary.
+            dt = {}
+            for i, h in enumerate(header):
+                dt[h] = t[i]
             # Calculate the differenciate of the day.
-            diff = price_end - price_begin
+            diff = float(dt['終値']) - float(dt['始値'])
             if diff > 0:
                 message = 'up'
             elif diff < 0:
@@ -59,7 +66,7 @@
             else:
                 message = 'same'
             # Write out day, up/down/same, and diff.
-            print('{}\t{:5}\t{}'.format(day, message, round(diff, 2)))
+            print('{}\t{:5}\t{}'.format(dt['日付'], message, round(diff, 2)))


 def main():

«  標準モジュール csv を使う   ::   Contents   ::   組込み関数 (zip / dict) を使う  »