onaka0_0suita
@onaka0_0suita (m- yuto)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Python3】csvファイル 関数について

Python初学者ですが、学習のために既存プログラムの改修を行いたいと考えております。
行いたい内容は、複数のcsvファイルを読み込み、データを整理した後、一つのファイルに書き出しです。

解決したいこと

まずは、下記に記載しているプログラムの「# CSVファイルを読み込んで辞書のリストで返す関数を実装する」というread_csvの箇所を関数や定数を用いて実装したいと思っております。
基本的にはcsvモジュールなどモジュールの追加などは行わず実装したいと思っております。
私の方で調べながら実装してみましたが、csvモジュールを使用しないやり方が出てこず、解決まで運ぶことが出来ませんでした。

ファイルは下記になります。

商品のマスターデータ
→/input/items.csv
商品の売上データ(日毎に別ファイル)
→/input/sales_raw_20161030.csv
 /input/sales_raw_20161101.csv
 ...
 /input/sales_raw_20161106.csv
書き出し後の売上データ
→/output/sales.csv

商品マスターデータ(items.csv)の中身は下記になります。

商品ID、商品名、商品価格

商品の売上データ(sales_raw_YYYYmmdd.csv)の中身は下記になります。

購入ID、ユーザーID、商品ID、個数、販売日時

書き出し後のデータ(sales.csv)は下記になります。

購入ID、ユーザーID、商品ID、商品名、商品価格、個数、販売日時

*商品の売上データの商品IDと商品マスターデータの商品IDを紐付け、紐付く商品名と商品価格を出力させます。

現状のプログラム

main.py
import os
import re

ITEMS_DATA = 'input/items.csv'
ITEMS_COLUMNS = ['item_id', 'name', 'price']

SALES_RAW_REGEX = re.compile(r'^sales_raw_(\d{4})(\d{2})(\d{2}).csv$')
SALES_COLUMNS = ['purchase_id', 'user_id', 'item_id',
                 'item_name', 'item_price', 'amount', 'sold_at']
SALES_DATA = 'output/sales.csv'

ENCODING = 'utf-8'
CSV_INPUT = input('')

# CSVファイルを読み込んで辞書のリストで返す関数を実装する
def read_csv(f, columns):
    pass

# Step5では以下の状態で実装する
def write_csv(f, data, columns):
    pass

# Step5では以下の状態で実装する
def read_items():
    pass

# Step5では以下の状態で実装する
def read_sales_raw(target_year, target_month):
    pass

# Step5では以下の状態で実装する
def write_sales(sales):
    pass

def main():
    # 商品のマスターデータ読み込み
    items = {}
    with open(ITEMS_DATA, encoding=ENCODING) as f:
        for row in read_csv(f, ITEMS_COLUMNS):
        # CSVファイルを読み込んで辞書のリストで返す関数使う
        item_id, item_name, item_price = row.rstrip().split(',')
        items[item_id] = {
            'item_name': item_name,
            'item_price': item_price
        }

    # 売上の生データを読み込んで、まとめる
    sales = []
    for filename in os.listdir('input/'):
        # * 対象の月のデータのみ読み込み
        if re.match(CSV_INPUT, filename):
            with open(os.path.join('input/', filename), encoding=ENCODING) as f:
                for row in f:
                    data = row.rstrip().split(',')
                    # 商品の情報を追加する
                    item_id = data[2]
                    if item_id in items:
                        sales.append({
                                'purchase_id': data[0],
                                'user_id': data[1],
                                'item_id': data[2],
                                'item_name': items[item_id]['item_name'],
                                'item_price': items[item_id]['item_price'],
                                'amount': data[3],
                                'sold_at': data[4]
                            })

    # まとめた売上データを書き出し
    m = SALES_RAW_REGEX.search(filename)
    if len(sales) == 0:
        print('書き出しファイルがありません')
        return
    SALES_COLUMNS
    with open(SALES_DATA, mode='w', encoding=ENCODING) as f:
        for row in sales:
            row_str = ','.join(str(row[column]) for column in SALES_COLUMNS)
            f.write(row_str + '\n')

if __name__ == "__main__":
    main()

自分で試したこと

商品マスターデータのwith文をread_csv関数内に記述していたのですが、その場合、関数の使い回しが出来ないので、「商品マスターデータ読み込み」内に記述したり、read_csvの引数に問題が生じたりしていたので、修正を行ったりしましたが、結局解決できないまま時間だけが過ぎてしまうので、ご教授頂けますと幸いです。
read_csv関数に関しては、csvモジュールを使用したやり方はたくさんサイトに載っているのですが、今回はcsvモジュールを使わないで実装なので、参考になるサイトも見当たらず、何を記述すると良いのかも分からない状況です。

read_csvで返したい情報としては下記のようなものになります。
[{'item_id': '1', 'name': '掃除機', 'price': '4980'}, {'item_id': '2', 'name': '扇風機', 'price': '2980'}......]

その他に何か必要な情報などありましたら、提示いたします。
大変恐縮ではございますが、ご教授頂けますと幸いです。
よろしくお願いいたします。

0

1Answer

csvモジュールを使わずに読み込むのは現状のプログラムでできているはずです。
返したい情報として示されているものは、こんな感じでできます。

def read_csv(path, columns):
    with open(path) as f:
        rows = [line.rstrip().split(",") for line in f]

    return [{column: row for column, row in zip(columns, row)} for row in rows]


def main():
    items = read_csv(ITEMS_DATA, ITEMS_COLUMNS)

read_csvは第1引数にファイルオブジェクトではなくパスを受け取るようにし、with文を関数内に記述しています。後は内包表記でいい感じに。
読み込むcsvファイルの列数とcolumnsの要素数が一致していれば問題なく動きます。

ただ、これだと現状のプログラムのitemsと同じにはなりませんよ。

# 現状のプログラムのitems
items = {"1": {"name": "掃除機", "price": "4980"}, ...}

item_id = "1"
item[item_id]  # => {"name", "掃除機", "price": "4980"}

# 要求しているitems
items = [{"item_id": "1", "name": "掃除機", "price": "4980"}, ...]

item_id = "1"
item[item_id]  # => エラー

後のコードを大幅に書き換える必要があると思います。

現状のプログラムと同じ様にやりたいなら、こんな感じでしょうか

def read_csv(path):
    with open(path) as f:
        return [line.rstrip().split(",") for line in f]


def read_items():
    items = {}
    for row in read_csv(ITEM_DATA):
        item_id, item_name, item_price = row
        items[item_id] = {"name": item_name, "price": item_price}

    return items


def main():
    items = read_items()

これならread_csv関数は売上生データ読み込みでも再利用可能ですし、書き込みデータの作成も現状のプログラムと同様のロジックでできると思います。
とりあえず質問はread_csv関数に関してだけみたいなので、後は頑張ってください。

1Like

Comments

  1. @onaka0_0suita

    Questioner

    akarinS様
    ご回答下さり、誠にありがとうございます。
    また、ご親切に分かりやすくご説明頂き、ありがとうございます。
    頂いた内容の前者と後者を視野に入れてどちらが良いのか考えて実装してみたいと思います。
    本当に躓いておりましたので、大きな一歩になりました。
    ありがとうございます!

Your answer might help someone💌