Python中級 - 解答

Python中級のチャレンジ

そこそこ複雑な注文集計処理を作る演習問題 - 3問目

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
from datetime import datetime, date
import os
import re

ITEM_TSV_PATH = 'items.tsv'
ORDER_DIR = 'order/'
FAILURE_DIR = 'failure/'
DELIVERY_DIR = 'delivery/'


class Item:
    """ 1商品に対応するクラス
    """
    def __init__(self, item_id, name, price):
    self.item_id = item_id
    self.name = name
    self.price = price


class Items:
    def __init__(self, items):
    self.items = items

    def has_id(self, item_id):
    """ item_id をもつ商品が存在するかチェックする
    """
    for item in self.items:
        if item.item_id == item_id:
        return True
    return False


class Order:
    AMOUNT_RE = re.compile(r'^[0-9]+$')
    TEL_RE = re.compile(r'^[0-9]{2,4}-[0-9]{4}-[0-9]{4}$')

    def __init__(self, item_id, amount, shipping_address, tel_number,
         fullname, shipping_date_str, order_file):
    self.item_id = item_id
    self.amount = amount
    self.shipping_address = shipping_address
    self.tel_number = tel_number
    self.fullname = fullname
    self.shipping_date_str = shipping_date_str
    self.order_file = order_file

    self.amount_int = None
    self.shipping_date = None

    def validate(self, items):
    """ 各注文の値が正しいかバリデーションチェックする。OKの場合True、NGの場合False
    """
    if not items.has_id(self.item_id):
        return False
    if not self.AMOUNT_RE.search(self.amount):
        return False
    self.amount_int = int(self.amount)
    if self.amount_int <= 0:
        return False
    if not self.shipping_address:
        return False
    if not self.TEL_RE.search(self.tel_number):
        return False
    if not self.fullname:
        return False
    try:
        self.shipping_date = datetime.strptime(self.shipping_date_str, '%Y-%m-%d')
    except ValueError:
        return False
    return True

    def row_string(self):
    return ','.join((
        self.item_id,
        self.amount,
        self.shipping_address,
        self.tel_number,
        self.fullname,
        self.shipping_date_str,
        self.order_file,
    ))


def load_items():
    """ ITEM_TSV_PATHのTSVからItemsを作る
    """
    items = []
    with open(ITEM_TSV_PATH, encoding='utf-8') as f:
    for row in f:
        item_id, name, price = row.split('\t')
        item = Item(item_id.strip(), name.strip(), price.strip())
        items.append(item)
    return Items(items)


def load_orders(target_date):
    """ ORDER_DIR のCSVからOrderのリストを作る

    * 各値の前後から空白を除外する
    """
    date_str = target_date.strftime('%Y%m%d')
    orders = []
    for filename in os.listdir(ORDER_DIR):
    if date_str not in filename:
        # 対象日でないファイルは無視する
        continue

    filepath = os.path.join(ORDER_DIR, filename)
    with open(filepath, encoding='utf-8') as f:
        for row in f:
        item_id, amount, address, tel, name, shipping_date = row.split(',')
        order = Order(
            item_id.strip(),
            amount.strip(),
            address.strip(),
            tel.strip(),
            name.strip(),
            shipping_date.strip(),
            filename,
        )
        orders.append(order)
    return orders


def write_deliver_orders(orders):
    """ Orderのリストを受け取って日別注文ファイルに書き込み
    """
    # 宅配日毎に集計。ファイルをオープンする回数を減らすため事前にまとめる
    date_orders = {}
    for order in orders:
    if order.shipping_date in date_orders:
        date_orders[order.shipping_date].append(order)
    else:
        date_orders[order.shipping_date] = [order]

    for d, day_orders in date_orders.items():
    filename = 'delivery_{}.csv'.format(d.strftime('%Y%m%d'))
    filepath = os.path.join(DELIVERY_DIR, filename)
    with open(filepath, 'a', encoding='utf-8') as f:
        for order in day_orders:
        f.write(order.row_string() + '\n')


def write_failure_orders(orders, order_date):
    """ Orderのリストを受け取って注文受付失敗ファイルに書き込み
    """


def main(target_date=None):
    """ 毎日の注文集計用スクリプト

    1. 商品マスター読み込み
    2. 当日分の注文受付ファイル読み込み
    3. 注文をバリデーションチェック
    4. 日別注文ファイル書き込み
    5. 注文受付失敗ファイル書き込み
    """
    target_date = target_date or date.today()

    items = load_items()
    orders = load_orders(target_date)
    validated_orders = []
    failed_orders = []
    for order in orders:
    if order.validate(items):
        validated_orders.append(order)
    else:
        failed_orders.append(order)

    if validated_orders:
    write_deliver_orders(validated_orders)
    if failed_orders:
    write_failure_orders(failed_orders, target_date)


if __name__ == '__main__':
    main(date(2016, 12, 14))  # あくまで動作確認用に日付を指定している