Python中級 - Python中級のチャレンジ - そこそこ複雑な注文集計処理を作る演習問題 - 4問目

main.py の4問目終了時の解答コードです。

  1from datetime import datetime, date
  2import os
  3import re
  4
  5ITEM_TSV_PATH = 'items.tsv'
  6ORDER_DIR = 'order/'
  7FAILURE_DIR = 'failure/'
  8DELIVERY_DIR = 'delivery/'
  9
 10
 11class Item:
 12    """ 1商品に対応するクラス
 13    """
 14    def __init__(self, item_id, name, price):
 15    self.item_id = item_id
 16    self.name = name
 17    self.price = price
 18
 19
 20class Items:
 21    def __init__(self, items):
 22    self.items = items
 23
 24    def has_id(self, item_id):
 25    """ item_id をもつ商品が存在するかチェックする
 26    """
 27    for item in self.items:
 28        if item.item_id == item_id:
 29        return True
 30    return False
 31
 32
 33class Order:
 34    AMOUNT_RE = re.compile(r'^[0-9]+$')
 35    TEL_RE = re.compile(r'^[0-9]{2,4}-[0-9]{4}-[0-9]{4}$')
 36
 37    def __init__(self, item_id, amount, shipping_address, tel_number,
 38         fullname, shipping_date_str, order_file):
 39    self.item_id = item_id
 40    self.amount = amount
 41    self.shipping_address = shipping_address
 42    self.tel_number = tel_number
 43    self.fullname = fullname
 44    self.shipping_date_str = shipping_date_str
 45    self.order_file = order_file
 46
 47    self.amount_int = None
 48    self.shipping_date = None
 49
 50    def validate(self, items):
 51    """ 各注文の値が正しいかバリデーションチェックする。OKの場合True、NGの場合False
 52    """
 53    if not items.has_id(self.item_id):
 54        return False
 55    if not self.AMOUNT_RE.search(self.amount):
 56        return False
 57    self.amount_int = int(self.amount)
 58    if self.amount_int <= 0:
 59        return False
 60    if not self.shipping_address:
 61        return False
 62    if not self.TEL_RE.search(self.tel_number):
 63        return False
 64    if not self.fullname:
 65        return False
 66    try:
 67        self.shipping_date = datetime.strptime(self.shipping_date_str, '%Y-%m-%d')
 68    except ValueError:
 69        return False
 70    return True
 71
 72    def row_string(self):
 73    return ','.join((
 74        self.item_id,
 75        self.amount,
 76        self.shipping_address,
 77        self.tel_number,
 78        self.fullname,
 79        self.shipping_date_str,
 80        self.order_file,
 81    ))
 82
 83
 84def load_items():
 85    """ ITEM_TSV_PATHのTSVからItemsを作る
 86    """
 87    items = []
 88    with open(ITEM_TSV_PATH, encoding='utf-8') as f:
 89    for row in f:
 90        item_id, name, price = row.split('    ')
 91        item = Item(item_id.strip(), name.strip(), price.strip())
 92        items.append(item)
 93    return Items(items)
 94
 95
 96def load_orders(target_date):
 97    """ ORDER_DIR のCSVからOrderのリストを作る
 98
 99    * 各値の前後から空白を除外する
100    """
101    date_str = target_date.strftime('%Y%m%d')
102    orders = []
103    for filename in os.listdir(ORDER_DIR):
104    if date_str not in filename:
105        # 対象日でないファイルは無視する
106        continue
107
108    filepath = os.path.join(ORDER_DIR, filename)
109    with open(filepath, encoding='utf-8') as f:
110        for row in f:
111        item_id, amount, address, tel, name, shipping_date = row.split(',')
112        order = Order(
113            item_id.strip(),
114            amount.strip(),
115            address.strip(),
116            tel.strip(),
117            name.strip(),
118            shipping_date.strip(),
119            filename,
120        )
121        orders.append(order)
122    return orders
123
124
125def write_deliver_orders(orders):
126    """ Orderのリストを受け取って日別注文ファイルに書き込み
127    """
128    # 宅配日毎に集計。ファイルをオープンする回数を減らすため事前にまとめる
129    date_orders = {}
130    for order in orders:
131    if order.shipping_date in date_orders:
132        date_orders[order.shipping_date].append(order)
133    else:
134        date_orders[order.shipping_date] = [order]
135
136    for d, day_orders in date_orders.items():
137    filename = 'delivery_{}.csv'.format(d.strftime('%Y%m%d'))
138    filepath = os.path.join(DELIVERY_DIR, filename)
139    with open(filepath, 'a', encoding='utf-8') as f:
140        for order in day_orders:
141        f.write(order.row_string() + '\n')
142
143
144def write_failure_orders(orders, order_date):
145    """ Orderのリストを受け取って注文受付失敗ファイルに書き込み
146    """
147    filename = 'failure_{}.csv'.format(order_date.strftime('%Y%m%d'))
148    filepath = os.path.join(FAILURE_DIR, filename)
149    with open(filepath, 'a', encoding='utf-8') as f:
150    for order in orders:
151        f.write(order.row_string() + '\n')
152
153
154def main(target_date=None):
155    """ 毎日の注文集計用スクリプト
156
157    1. 商品マスター読み込み
158    2. 当日分の注文受付ファイル読み込み
159    3. 注文をバリデーションチェック
160    4. 日別注文ファイル書き込み
161    5. 注文受付失敗ファイル書き込み
162    """
163    target_date = target_date or date.today()
164
165    items = load_items()
166    orders = load_orders(target_date)
167    validated_orders = []
168    failed_orders = []
169    for order in orders:
170    if order.validate(items):
171        validated_orders.append(order)
172    else:
173        failed_orders.append(order)
174
175    if validated_orders:
176    write_deliver_orders(validated_orders)
177    if failed_orders:
178    write_failure_orders(failed_orders, target_date)
179
180
181if __name__ == '__main__':
182    main(date(2016, 12, 14))  # あくまで動作確認用に日付を指定している
当コンテンツの知的財産権は株式会社ビープラウドに所属します。詳しくは利用規約をご確認ください。