LoginSignup
0
0

More than 3 years have passed since last update.

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

Posted at

はじめに

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())

まとめ

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

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
  3. You can use dark theme
What you can do with signing up
0
0