Python School 2.0.0 documentation

組込み関数 (zip / dict) を使う

«  ユニコード文字列を考慮する   ::   Contents   ::   クラスを定義する  »

組込み関数 (zip / dict) を使う

前の章でファイルの1行目にヘッダー行がある場合のファイル処理を実装しました。 今度は、組込み関数 (zip / dict) を使って実現してみましょう。

csv-1.csv に英語でヘッダー行を追加して csv-4.csv とします。

day,price_begin,price_max,price_min,price_end
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

zip のテスト

コンソールで python を実行して REPL (Read–eval–print loop) モードに入りましょう。 インタラクティブに動作を確認できます。

2つのタプル “a”, “b” を用意して、それらに zip() を適用させます。

$ python
>>> a = ("A", "B", "C")
>>> b = (1, 2, 3)
>>> zip(a, b)
<zip object at 0x1018e5320>
>>> for t in zip(a, b):
...     t
...
('A', 1)
('B', 2)
('C', 3)

それぞれの同じインデックスにある要素をまとめていることが分かりました。

dict のテスト

次に、 dict() を使ってみます。 引数には配列の配列(多次元配列)を渡します。

$ python
>>> a = (('A', 1), ('B', 2), ('C', 3))
>>> dict(a)
{'A': 1, 'B': 2, 'C': 3}

先ほどの zip() と合わせて考えると、 2つの配列から、それぞれのインデックス番号でまとめた辞書オブジェクトを生成できました。

実装の改修

列の情報を辞書で保持し、名称とデータ型を持つようにします。 そして、入力行に対して zipdict を適用することで辞書オブジェクトに変換します。 辞書で扱うと、ソースコード内のマジックナンバーを少なく保てるメリットがあります。

csv-2.py を改修して csv-4.py としています。

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

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

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

# Declare fields definition.
FIELDS = (
    {'id': 'day', 'type': 'string'},
    {'id': 'price_begin', 'type': 'float'},
    {'id': 'price_max', 'type': 'float'},
    {'id': 'price_min', 'type': 'float'},
    {'id': 'price_end', 'type': 'float'}
)


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", dest="header", default=False,
                        action="store_true", help="contains header row")
    parser.add_argument("--encoding", dest="encoding", default='utf-8',
                        help="Encoding of input file")
    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.
    """
    header = tuple(map(lambda f: f['id'], FIELDS))
    with open(args.filename[0], encoding=args.encoding) as fp:
        reader = csv.reader(fp)  # Instantiate CSV reader with file pointer.
        if args.header:
            _ = next(reader)
            logging.info('Skip header row: %s', _)
        else:
            logging.info('No header row is to be in input file.')
        for t in reader:
            dt = dict(zip(header, t))  # Bind header and row
            # Calculate the differenciate of the day.
            diff = float(dt['price_end']) - float(dt['price_begin'])
            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['day'], 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 :

ヘッダー行付きの csv-4.csv を読み込む場合は –header オプションを付けて実行し、 ヘッダー行無しの csv-1.csv を読み込む場合はオプションを付けずに実行します。

csv-2.py からの差分は以下の通りです。 組み込み関数 next() を使って1行だけ読み込む処理も記載していますので、確認しておきましょう。

@@ -8,6 +8,15 @@
 import csv  # import standard "csv" module
 import logging

+# Declare fields definition.
+FIELDS = (
+    {'id': 'day', 'type': 'string'},
+    {'id': 'price_begin', 'type': 'float'},
+    {'id': 'price_max', 'type': 'float'},
+    {'id': 'price_min', 'type': 'float'},
+    {'id': 'price_end', 'type': 'float'}
+)
+

 def parse_args():
     """Parse arguments and set up logging verbosity.
@@ -26,6 +35,10 @@
     parser.add_argument("-q", "--quiet", dest="quiet", default=False,
                         action="store_true", help="quiet mode")
     # Add this line from boilerplate.
+    parser.add_argument("--header", dest="header", default=False,
+                        action="store_true", help="contains header row")
+    parser.add_argument("--encoding", dest="encoding", default='utf-8',
+                        help="Encoding of input file")
     parser.add_argument("filename", nargs=1, help="CVS file path")

     args = parser.parse_args()
@@ -41,17 +54,18 @@
 def process(args):
     """Parse daily Tokyo stock prices, and calculate up/down.
     """
-    with open(args.filename[0]) as fp:
+    header = tuple(map(lambda f: f['id'], FIELDS))
+    with open(args.filename[0], encoding=args.encoding) as fp:
         reader = csv.reader(fp)  # Instantiate CSV reader with file pointer.
+        if args.header:
+            _ = next(reader)
+            logging.info('Skip header row: %s', _)
+        else:
+            logging.info('No header row is to be in input file.')
         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])
+            dt = dict(zip(header, t))  # Bind header and row
             # Calculate the differenciate of the day.
-            diff = price_end - price_begin
+            diff = float(dt['price_end']) - float(dt['price_begin'])
             if diff > 0:
                 message = 'up'
             elif diff < 0:
@@ -59,7 +73,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['day'], message, round(diff, 2)))


 def main():

宿題

  1. フィールド定義に沿ってデータ型の変換も実施してください。 つまり、相場の動きを以下のように記述できるようにしてください。

    # diff = float(dt['price_end']) - float(dt['price_begin'])
    diff = dt['price_end'] - dt['price_begin']
    
  2. –header オプションでヘッダー行の有無を指定させていますが、自動的に認識させてください。

  3. csv モジュールには DictReader クラスがあります。このクラスを使って実装してください。

  4. collections モジュールの namedtuple() を使って実装してください。

«  ユニコード文字列を考慮する   ::   Contents   ::   クラスを定義する  »