search
LoginSignup
0

More than 1 year has passed since last update.

posted at

Effective Python 学習備忘録 6日目 【6/100】

はじめに

Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです!

今回学習する教材

今日の進捗

  • 進行状況:56-60ページ
  • 第3章:クラスと継承
  • 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。

辞書やタプルで記録管理するよりもヘルパークラスを使う

辞書やタプルで記録管理するのは、使いやすい反面、複雑になってしまうと可読性を著しく下げてしまう。
一例として、あるお店の売り上げを記録するクラスを定義する。

class SimpleSalesRecord(object):
    def __init__(self):
        self._record = {}

    def add_shop_id(self, shop_id):
        self._record[shop_id] = []

    def report_record(self, shop_id, price):
        self._record[shop_id].append(price)

    def average_price(self, shop_id):
        records = self._record[shop_id]
        return sum(records) / len(records)

record = SimpleSalesRecord()
record.add_shop_id(111)
record.report_record(111, 90)
record.report_record(111, 80)
print(record.average_price(111))

出力結果

850.0

このクラスを拡張して、商品ごとの売上を管理するようにする場合を考える。

class ByitemSalesRecord(object):
    def __init__(self):
        self._record = {}

    def add_shop_id(self, shop_id):
        self._record[shop_id] = {}         # リストから辞書に変更

    def report_record(self, shop_id, item, price):
        by_item = self._record[shop_id]
        record_list = by_item.setdefault(item, [])
        record_list.append(price)

    def average_price(self, shop_id):
        by_item = self._record[shop_id]
        total, count = 0, 0
        for prices in by_item.values():
            total += sum(prices)
            count += len(prices)
        return total / count

record = ByitemSalesRecord()
record.add_shop_id(111)                     # shop_idが111の店を追加
record.report_record(111, 'Apple', 100)     # 111店の売り上げを追加
record.report_record(111, 'Apple', 120)     # 111店の売り上げを追加

print(record.average_price(111))   # 111店の売り上げの平均を算出

出力結果

Apple: 110.0
Orange: 90.0

さらに、賞味期限が近いものは値引きするという機能を実装する。

class DiscountSalesRecord(object):
    def __init__(self):
        self._record = {}

    def add_shop_id(self, shop_id):
        self._record[shop_id] = {}         # リストから辞書に変更

    def report_record(self, shop_id, item, price, weight):
        by_item = self._record[shop_id]
        record_list = by_item.setdefault(item, [])
        record_list.append((price, weight))

    def average_price(self, shop_id):
        by_item = self._record[shop_id]
        price_sum, price_count = 0, 0
        for item, prices in by_item.items():
            item_avg, total_weight = 0, 0
            for price, weight in prices:
                price_sum += price * weight
                price_count += 1
        return price_sum / price_count

record = DiscountSalesRecord()
record.add_shop_id(111)                          # shop_idが111の店を追加
record.report_record(111, 'Apple', 100, 0.8)     # 位置引数が何を意味するのか明確ではない
record.report_record(111, 'Apple', 120, 1) 
print(record.average_price(111))                 # 111店の売り上げの平均を算出

average_priceメソッドでは、ループの中にループがあり、読みにくくなっている。このような複雑さが生じた場合は、辞書とタプルからクラス階層にシフトすべき頃合いである。

クラスへのリファクタリング

上記のコードをクラスへリファクタリングしたコードは以下のようになる。
collectionモジュールのnamedtupleは通常のタプルと異なり、キーワード引数でも指定可能。

import collections

Record = collections.namedtuple('Record', ('price', 'weight'))

class Item(object):
    def __init__(self):
        self._records = []

    def report_record(self, price, weight=1):
        self._records.append(Record(price, weight))

    def average_price(self):
        total, total_weight = 0, 0
        for record in self._records:
            total += record.price * record.weight
        return total / len(self._records)

class Shop_ID(object):
    def __init__(self):
        self._shop_items = {}

    def item(self, name):
        if name not in self._shop_items:
            self._shop_items[name] = Item()
        return self._shop_items[name]

    def average_price(self):
        total, count = 0, 0
        for shop_item in self._shop_items.values():
            total += shop_item.average_price()
            count += 1
        return total / count

class SalesRecord(object):
    def __init__(self):
        self._shop_id = {}

    def shop_id(self, name):
        if name not in self._shop_id:
            self._shop_id[name] = Shop_ID()
        return self._shop_id[name]

record = SalesRecord()
shop_111 = record.shop_id(111)
apple = shop_111.item('Apple')
apple.report_record(100, 0.8)
apple.report_record(100)
print(shop_111.average_price())

まとめ

  • 値が他の辞書や長いタプルであるような辞書は作らない
  • 辞書が複雑になったら、複数のヘルパークラスを使うように変更する

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0