Python中級 - Python中級のチャレンジ - そこそこ複雑な注文集計処理を作る演習問題 - 3問目¶
main.py
の3問目終了時の解答コードです。
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('\t')
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
148
149def main(target_date=None):
150 """ 毎日の注文集計用スクリプト
151
152 1. 商品マスター読み込み
153 2. 当日分の注文受付ファイル読み込み
154 3. 注文をバリデーションチェック
155 4. 日別注文ファイル書き込み
156 5. 注文受付失敗ファイル書き込み
157 """
158 target_date = target_date or date.today()
159
160 items = load_items()
161 orders = load_orders(target_date)
162 validated_orders = []
163 failed_orders = []
164 for order in orders:
165 if order.validate(items):
166 validated_orders.append(order)
167 else:
168 failed_orders.append(order)
169
170 if validated_orders:
171 write_deliver_orders(validated_orders)
172 if failed_orders:
173 write_failure_orders(failed_orders, target_date)
174
175
176if __name__ == '__main__':
177 main(date(2016, 12, 14)) # あくまで動作確認用に日付を指定している