Pythonで自動化ツールを作ろう

このハンズオンでは、Pythonを使って売上データの集計を自動化するツールを実際にハンズオン形式で手を動かしながら体験します。

  • csvモジュールによるCSVファイルの読み込み
  • クラスを使った売上データの管理と集計
  • json.dump()によるJSON形式でのレポート出力
  • try/exceptによるエラーハンドリング

1. 事前準備

この講座のハンズオンでは、以下のツールが必要です。まだ準備できていない場合は、リンク先の手順に沿って準備をお願いします。

2. ハンズオンの概要

このハンズオンでは、CSVファイルに記録された売上データを読み込み、カテゴリ別に集計してJSON形式のレポートを出力するツールを作成します。

2.1 自動化をする意義

実際の業務では、様々な定型業務が発生します。それらの業務をプログラミングで自動化し、業務を効率化できることは、プログラミングを学ぶ大きなメリットの一つです。

例えば今回のケースでは、CSVファイルとして管理している売上データを、商品のカテゴリごとに集計して、レポートとして出力するという定型業務が発生していたと想定します。

こういった業務を毎日手動で行うと、それだけで日々数分、あるいは数時間の時間が取られます。それだけではなく、集計ミスや転記ミスなど、意図せぬトラブルにも見舞われます。これらを自動化することで、作業時間を削減して効率を上げると同時に、ヒューマンエラーを防止できます。

💡 ポイント
自動化のメリットは時間短縮だけではありません。手作業では、データの転記ミスや計算間違い、ファイルの保存先を間違えるといったヒューマンエラーが起こりがちです。プログラムで自動化すれば、同じ処理を何度実行しても常に同じ結果が得られるため、こうしたミスを防止できます。

2.2 今回作成するツール

今回は、売上データの集計ツールを作成します。CSVファイルから売上データを読み込み、商品カテゴリごとの販売個数と売上合計を集計し、その結果をJSON形式のファイルに書き出すという一連の処理を自動化します。

今回作成するツールの処理の流れを図に示します。

sequenceDiagram
    participant CSV as CSVファイル
    participant Sale as Saleオブジェクト
    participant Agg as 集計処理
    participant CS as CategorySummary
    participant JSON as JSONファイル

    CSV->>Sale: データを読み込み
    Sale->>Agg: カテゴリ別にグループ化
    Agg->>CS: 販売個数・売上合計を算出
    CS->>JSON: レポート辞書に変換して出力

具体的には、以下のようなCSVの売上データを入力として受け取り、

date,product,category,quantity,unit_price
2025-01-06,ノート,文房具,10,200
2025-01-06,コーヒー,飲料,8,300
...

商品カテゴリ(「文房具」「飲料」など)ごとに販売個数と売上金額を合算し、以下のようなJSON形式のレポートファイルを出力します。

[
    {
        "category": "文房具",
        "total_quantity": 39,
        "total_amount": 6200
    },
    {
        "category": "飲料",
        "total_quantity": 35,
        "total_amount": 7200
    }
]

さらに、実行時にコマンドライン引数で目標金額を指定すると、売上合計と目標金額を比較して達成状況を判定します。例えばpython main.py 15000と実行すると、目標15,000円に対して「目標達成」「あと一歩」「未達成」のいずれかを表示します。

2.3 今回のハンズオンで得られるもの

このツールを作成する過程で、これまでの講座で学んだPythonの要素を組み合わせて使います。

  • csvモジュールを使ってCSVファイルを1行ずつ辞書として読み込む
  • コマンドライン引数(sys.argv)から、実行時に目標金額をパラメータとして受け取る
  • if/elif/elseで売上合計と目標金額を比較し、達成状況に応じたメッセージを表示する
  • 読み込んだ売上データをリスト(sales)にまとめて管理する
  • forループでリストの各要素を1件ずつ処理し、集計やファイル出力を行う
  • 辞書を使ってカテゴリ名をキーに集計結果を管理する
  • クラスを使って売上データ(Sale)や集計結果(CategorySummary)を、属性とメソッドを持つオブジェクトとして扱う
  • try/exceptでファイルが見つからない場合などのエラーに安全に対処する

個々の要素はこれまでの講座で学んできたものですが、それらを組み合わせて一つのツールとして動かすことが、このハンズオンのポイントです。

3. プロジェクトの準備

それでは、早速ハンズオンを行っていきます。まずはプロジェクトの準備から行います。

3.1 フォルダの作成

まず、任意の場所にsales-reportフォルダを作成します。

sales-report/  ← このフォルダを作成

作成したフォルダをVisual Studio Codeで開きます。Visual Studio Codeのメニューから「ファイル」→「フォルダーを開く」を選択し、作成したフォルダを開いてください。

3.2 売上データの作成

集計対象となる売上データのCSVファイルを作成します。Visual Studio Codeのエクスプローラーで右クリックし、「新しいファイル」を選択してsales.csvという名前でファイルを作成してください。

sales-report/
└── sales.csv  ← このファイルを作成

作成したファイルに以下の内容を記述して保存します。これは、3日間の売上データを模したサンプルデータです。

date,product,category,quantity,unit_price
2025-01-06,ノート,文房具,10,200
2025-01-06,ボールペン,文房具,5,150
2025-01-06,コーヒー,飲料,8,300
2025-01-07,ノート,文房具,3,200
2025-01-07,お茶,飲料,12,150
2025-01-07,コーヒー,飲料,5,300
2025-01-08,消しゴム,文房具,6,100
2025-01-08,ボールペン,文房具,15,150
2025-01-08,お茶,飲料,10,150

各列の意味は以下のとおりです。

列名 説明
date 売上日
product 商品名
category 商品カテゴリ
quantity 数量
unit_price 単価(円)
📝 CSVとは
CSV(Comma-Separated Values)は、データをカンマ(,)で区切って記録するテキスト形式のファイルです。Excelなどの表計算ソフトでも開くことができ、データの受け渡しに広く使われています。1行目はヘッダ行(列名)、2行目以降がデータ行です。

3.3 メインスクリプトの作成

売上データを集計するスクリプトを作成します。Visual Studio Codeのエクスプローラーで右クリックし、「新しいファイル」を選択してmain.pyという名前で作成してください。

sales-report/
├── sales.csv
└── main.py  ← このファイルを作成

4. CSVファイルの読み込み

4.1 csvモジュールとは

Pythonの標準ライブラリには、CSVファイルの読み書きを行うためのcsvモジュールが用意されています。前の講座Python応用構文で学んだopen()split()でもCSVを読み込むことはできますが、csvモジュールを使うことで、より安全で簡潔にCSVファイルを扱えます。

📝 csvモジュールについて
csvモジュールはPythonの標準ライブラリに含まれており、追加のインストールは不要です。CSVファイルの読み込みにはcsv.reader()csv.DictReader()の2つの方法がありますが、この講座では列名でデータにアクセスできるcsv.DictReader()を使用します。

4.2 csv.DictReaderによる読み込み

csv.DictReader()は、CSVファイルの各行を辞書として読み込むクラスです。ヘッダ行の列名がキー、各データ行の値が値になります。基本的な構文は以下のとおりです。

import csv

with open("ファイル名", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["列名"])

csv.DictReader()にファイルオブジェクトを渡すと、ヘッダ行を自動的に読み取り、各データ行を辞書型のオブジェクトとして返します。row["列名"]のように列名をキーにして値にアクセスできます。

実際に試してみましょう。main.pyに以下の内容を記述して保存します。

import csv

with open("sales.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row)

Visual Studio Codeのメニューから「ターミナル」→「新しいターミナル」を選択してターミナルを開きます。main.pyを実行して動作を確認しましょう。

💡 ポイント
Pythonの実行コマンドは環境によって異なります。Windowsではpython main.py、macOSではpython3 main.pyを使用します。以降のハンズオンでは「main.pyを実行して」と記載しますので、お使いの環境に合わせてコマンドを読み替えてください。

以下のように各行が辞書として表示されれば成功です。

{'date': '2025-01-06', 'product': 'ノート', 'category': '文房具', 'quantity': '10', 'unit_price': '200'}
{'date': '2025-01-06', 'product': 'ボールペン', 'category': '文房具', 'quantity': '5', 'unit_price': '150'}
{'date': '2025-01-06', 'product': 'コーヒー', 'category': '飲料', 'quantity': '8', 'unit_price': '300'}
{'date': '2025-01-07', 'product': 'ノート', 'category': '文房具', 'quantity': '3', 'unit_price': '200'}
{'date': '2025-01-07', 'product': 'お茶', 'category': '飲料', 'quantity': '12', 'unit_price': '150'}
{'date': '2025-01-07', 'product': 'コーヒー', 'category': '飲料', 'quantity': '5', 'unit_price': '300'}
{'date': '2025-01-08', 'product': '消しゴム', 'category': '文房具', 'quantity': '6', 'unit_price': '100'}
{'date': '2025-01-08', 'product': 'ボールペン', 'category': '文房具', 'quantity': '15', 'unit_price': '150'}
{'date': '2025-01-08', 'product': 'お茶', 'category': '飲料', 'quantity': '10', 'unit_price': '150'}

CSVファイルの1行目(date,product,category,quantity,unit_price)がキーとして使われ、2行目以降の各データ行が辞書に変換されていることが確認できます。

💡 ポイント
CSVから読み込んだ値は、数値であってもすべて文字列型になります。上の出力で'quantity': '10'のように数値が引用符で囲まれていることからもわかります。計算に使う場合はint()float()で型変換が必要です。

5. 売上データクラスの定義

CSVから読み込んだデータは辞書として扱えますが、ここでは前の講座Python関数とクラスで学んだクラスを使って売上データを管理します。クラスを使うことで、データの構造が明確になり、sale.dateのようにわかりやすい形でデータにアクセスできるようになります。

5.1 Saleクラスの作成

売上データを管理するSaleクラスを定義します。このクラスはmain.pyから読み込んで使うため、別ファイルとして作成します。Visual Studio Codeのエクスプローラーで右クリックし、「新しいファイル」を選択してmodels.pyという名前で作成してください。

sales-report/
├── sales.csv
├── models.py  ← このファイルを作成
└── main.py

models.pyに以下の内容を記述して保存します。

class Sale:
    def __init__(self, date, product, category, quantity, unit_price):
        self.date = date
        self.product = product
        self.category = category
        self.quantity = quantity
        self.unit_price = unit_price
        self.amount = quantity * unit_price

Saleクラスの__init__メソッドで、売上日、商品名、カテゴリ、数量、単価を属性として保持し、売上金額(amount)をquantity * unit_priceで自動的に計算しています。

5.2 main.pyの更新

次に、main.pyからSaleクラスを読み込んで使います。main.pyを以下の内容に書き換えて保存します。

import csv
from models import Sale

# CSVファイルの読み込み
sales = []
with open("sales.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        sale = Sale(
            date=row["date"],
            product=row["product"],
            category=row["category"],
            quantity=int(row["quantity"]),
            unit_price=int(row["unit_price"])
        )
        sales.append(sale)

# 読み込み結果の確認
for sale in sales:
    print(f"{sale.date} {sale.product}: {sale.quantity}個 × {sale.unit_price}円 = {sale.amount}円")

追加したコードを解説します。

from models import Sale

この部分で、models.pyに定義したSaleクラスを読み込んでいます。これは前の講座Pythonライブラリで学んだモジュールのインポートと同じ仕組みです。models.pyというファイル名から.pyを除いたmodelsがモジュール名になります。

for row in reader:

forループで、CSVの各行を1行ずつ処理します。rowには{"date": "2025-01-06", "product": "ノート", "category": "文房具", "quantity": "10", "unit_price": "200"}のような辞書が入ります。

    sale = Sale(
        date=row["date"],
        product=row["product"],
        category=row["category"],
        quantity=int(row["quantity"]),
        unit_price=int(row["unit_price"])
    )

辞書から値を取り出し、Saleクラスのインスタンスを作成しています。row["date"]のように、ヘッダ名をキーにして値を取り出せます。CSVから読み込んだ値はすべて文字列のため、数量と単価はint()で整数に変換してから渡しています。

    sales.append(sale)

作成したSaleインスタンスを、リストsalesに追加しています。このループが完了すると、CSVの全行分のSaleインスタンスがsalesリストに格納されます。

for sale in sales:
    print(f"{sale.date} {sale.product}: {sale.quantity}個 × {sale.unit_price}円 = {sale.amount}円")

最後に、salesリストに格納された各Saleインスタンスの属性をprintで出力し、正しく読み込めたかを確認しています。sale.amountSaleクラスの__init__で自動計算された売上金額です。

main.pyを実行して動作を確認しましょう。以下のように各売上データが表示されます。

2025-01-06 ノート: 10個 × 200円 = 2000円
2025-01-06 ボールペン: 5個 × 150円 = 750円
2025-01-06 コーヒー: 8個 × 300円 = 2400円
2025-01-07 ノート: 3個 × 200円 = 600円
2025-01-07 お茶: 12個 × 150円 = 1800円
2025-01-07 コーヒー: 5個 × 300円 = 1500円
2025-01-08 消しゴム: 6個 × 100円 = 600円
2025-01-08 ボールペン: 15個 × 150円 = 2250円
2025-01-08 お茶: 10個 × 150円 = 1500円

sale.datesale.productsale.amountのように、属性名でデータにアクセスできています。

6. カテゴリ別の集計

ここからは、売上データをカテゴリ別に集計していきます。集計結果を保持するためのCategorySummaryクラスを新たに作成し、Saleのデータを集計します。

6.1 CategorySummaryクラスの作成

カテゴリごとの販売個数と売上合計を保持するCategorySummaryクラスを定義します。このクラスには、Saleのデータを受け取って集計するadd()メソッドを持たせます。

クラスにデータを加算するメソッドの基本的な構文は以下のとおりです。

class 集計クラス:
    def __init__(self, グループ名):
        self.グループ名 = グループ名
        self.合計 = 0

    def add(self, データ):
        self.合計 += データの値

__init__で合計の初期値を0に設定し、add()メソッドが呼ばれるたびにデータの値を加算していきます。

models.pyCategorySummaryクラスを追加します。Saleクラスの下に以下のコードを追加してください。

class Sale:
    def __init__(self, date, product, category, quantity, unit_price):
        self.date = date
        self.product = product
        self.category = category
        self.quantity = quantity
        self.unit_price = unit_price
        self.amount = quantity * unit_price


# ====== ↓ ここから追加 ======
class CategorySummary:
    def __init__(self, category):
        self.category = category
        self.total_quantity = 0
        self.total_amount = 0

    def add(self, sale):
        self.total_quantity += sale.quantity
        self.total_amount += sale.amount
# ====== ↑ ここまで追加 ======

CategorySummaryクラスの各部分を説明します。

  • __init__メソッドで、カテゴリ名と、販売個数の合計(total_quantity)・売上金額の合計(total_amount)の初期値0を設定しています
  • add()メソッドは、Saleインスタンスを受け取り、そのquantity(数量)とamount(金額)をそれぞれ加算します

6.2 main.pyの更新

次に、main.pyを更新してCategorySummaryを使ったカテゴリ別集計を行います。カテゴリ別に集計するには、辞書を使ってカテゴリ名をキー、CategorySummaryインスタンスを値として管理します。基本的な構文は以下のとおりです。

グループ = {}
for データ in データのリスト:
    キー = データのグループ名
    if キー not in グループ:
        グループ[キー] = 集計クラス(キー)
    グループ[キー].add(データ)

辞書にまだそのキーが存在しない場合は新しいインスタンスを作成し、add()メソッドでデータを加算していきます。

main.pyにカテゴリ別集計の処理を追加します。インポート文にCategorySummaryを追加し、CSV読み込みの後に集計と表示のコードを追加してください。

import csv
from models import Sale, CategorySummary  # ← CategorySummaryを追加

# CSVファイルの読み込み
sales = []
with open("sales.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        sale = Sale(
            date=row["date"],
            product=row["product"],
            category=row["category"],
            quantity=int(row["quantity"]),
            unit_price=int(row["unit_price"])
        )
        sales.append(sale)

# ====== ↓ ここから追加 ======
# カテゴリ別の集計
summaries = {}
for sale in sales:
    if sale.category not in summaries:
        summaries[sale.category] = CategorySummary(sale.category)
    summaries[sale.category].add(sale)

# 集計結果の表示
print("=== カテゴリ別売上集計 ===")
for summary in summaries.values():
    print(f"  {summary.category}: {summary.total_quantity}個 / {summary.total_amount}円")
# ====== ↑ ここまで追加 ======

追加した集計部分を解説します。

summaries = {}

カテゴリ別の集計結果を格納するための空の辞書を用意します。カテゴリ名("文房具""飲料"など)をキー、CategorySummaryインスタンスを値として管理します。

    if sale.category not in summaries:
        summaries[sale.category] = CategorySummary(sale.category)

if sale.category not in summariesで、そのカテゴリがまだ辞書に存在しないかを確認しています。初めて出てきたカテゴリの場合は、新しいCategorySummaryインスタンスを作成して辞書に登録します。

    summaries[sale.category].add(sale)

辞書から該当カテゴリのCategorySummaryを取り出し、add()メソッドで数量と金額を加算しています。同じカテゴリの売上データが繰り返し処理されるたびに、合計値が積み上がっていきます。

main.pyを実行して動作を確認しましょう。以下のようにカテゴリ別の集計結果が表示されます。

=== カテゴリ別売上集計 ===
  文房具: 39個 / 6200円
  飲料: 35個 / 7200円

文房具カテゴリの集計を確認すると、ノート(10個 + 3個)+ ボールペン(5個 + 15個)+ 消しゴム(6個)= 39個、金額は2,000 + 600 + 750 + 2,250 + 600 = 6,200円です。正しく集計されていることが確認できます。

7. JSONファイルへの出力

集計結果をJSON形式のファイルに出力します。ただし、前の講座で学んだjson.dump()は辞書やリストをJSONに変換できますが、CategorySummaryのようなクラスのインスタンスをそのまま渡すことはできません。そこで、インスタンスを辞書に変換するto_dict()メソッドを追加します。

7.1 to_dict()メソッドの追加

クラスのインスタンスを辞書に変換するメソッドの基本的な構文は以下のとおりです。

class クラス名:
    def to_dict(self):
        return {
            "キー1": self.属性1,
            "キー2": self.属性2,
        }

to_dict()メソッドは、インスタンスの属性を辞書にまとめて返します。これにより、json.dump()に渡せる形式に変換できます。

models.pyCategorySummaryクラスにto_dict()メソッドを追加します。add()メソッドの下に以下のコードを追加してください。

class CategorySummary:
    def __init__(self, category):
        self.category = category
        self.total_quantity = 0
        self.total_amount = 0

    def add(self, sale):
        self.total_quantity += sale.quantity
        self.total_amount += sale.amount

    # ====== ↓ ここから追加 ======
    def to_dict(self):
        return {
            "category": self.category,
            "total_quantity": self.total_quantity,
            "total_amount": self.total_amount
        }
    # ====== ↑ ここまで追加 ======

追加したto_dict()メソッドを解説します。

    def to_dict(self):
        return {
            "category": self.category,
            "total_quantity": self.total_quantity,
            "total_amount": self.total_amount
        }

インスタンスの属性を辞書にまとめて返しています。例えば、文房具カテゴリのCategorySummaryto_dict()を呼ぶと、{"category": "文房具", "total_quantity": 39, "total_amount": 6200}という辞書が返されます。この辞書をjson.dump()に渡すことで、JSON形式でファイルに出力できるようになります。

7.2 main.pyの更新

main.pyにJSON出力処理を追加します。インポート文にjsonを追加し、集計結果の表示の後にJSON出力のコードを追加してください。

import csv
import json  # ← 追加
from models import Sale, CategorySummary

# CSVファイルの読み込み
sales = []
with open("sales.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        sale = Sale(
            date=row["date"],
            product=row["product"],
            category=row["category"],
            quantity=int(row["quantity"]),
            unit_price=int(row["unit_price"])
        )
        sales.append(sale)

# カテゴリ別の集計
summaries = {}
for sale in sales:
    if sale.category not in summaries:
        summaries[sale.category] = CategorySummary(sale.category)
    summaries[sale.category].add(sale)

# 集計結果の表示
print("=== カテゴリ別売上集計 ===")
for summary in summaries.values():
    print(f"  {summary.category}: {summary.total_quantity}個 / {summary.total_amount}円")

# ====== ↓ ここから追加 ======
# JSONファイルへの出力
result = []
for summary in summaries.values():
    result.append(summary.to_dict())

with open("summary.json", "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=4)
print("\n集計結果を summary.json に出力しました")
# ====== ↑ ここまで追加 ======

追加したJSON出力部分を解説します。

result = []
for summary in summaries.values():
    result.append(summary.to_dict())

summaries辞書から各CategorySummaryインスタンスを取り出し、to_dict()で辞書に変換してリストにまとめています。この時点でresult[{"category": "文房具", "total_quantity": 39, ...}, {"category": "飲料", ...}]のようなリストになります。

with open("summary.json", "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=4)

json.dump()は前の講座で学んだとおり、Pythonのリストや辞書をJSON形式でファイルに書き出す関数です。ensure_ascii=Falseで日本語をそのまま出力し、indent=4で読みやすくインデントを付けています。

main.pyを実行して動作を確認しましょう。summary.jsonファイルが作成されるので、Visual Studio Codeのエクスプローラーからsummary.jsonを開いて内容を確認してください。以下のようなJSONが出力されていれば成功です。

[
    {
        "category": "文房具",
        "total_quantity": 39,
        "total_amount": 6200
    },
    {
        "category": "飲料",
        "total_quantity": 35,
        "total_amount": 7200
    }
]

カテゴリごとの販売個数と売上合計がJSON形式で出力されています。このJSONファイルは、他のプログラムやツールで読み込んで利用することができます。

8. 売上目標の判定

集計結果をただ表示するだけでなく、売上合計が目標金額に対してどの程度達成しているかを判定する機能を追加します。目標金額は前の講座で学んだコマンドライン引数sys.argv)で実行時に指定できるようにし、if/elif/elseで達成状況に応じたメッセージを表示します。

8.1 判定ロジックの追加

main.pyにコマンドライン引数の処理と売上目標の判定を追加します。インポート文にsysを追加し、JSON出力の後に判定ロジックを追加してください。

import csv
import json
import sys  # ← 追加
from models import Sale, CategorySummary

# CSVファイルの読み込み
sales = []
with open("sales.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        sale = Sale(
            date=row["date"],
            product=row["product"],
            category=row["category"],
            quantity=int(row["quantity"]),
            unit_price=int(row["unit_price"])
        )
        sales.append(sale)

# カテゴリ別の集計
summaries = {}
for sale in sales:
    if sale.category not in summaries:
        summaries[sale.category] = CategorySummary(sale.category)
    summaries[sale.category].add(sale)

# 集計結果の表示
print("=== カテゴリ別売上集計 ===")
for summary in summaries.values():
    print(f"  {summary.category}: {summary.total_quantity}個 / {summary.total_amount}円")

# JSONファイルへの出力
result = []
for summary in summaries.values():
    result.append(summary.to_dict())

with open("summary.json", "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=4)
print("\n集計結果を summary.json に出力しました")

# ====== ↓ ここから追加 ======
# コマンドライン引数から目標金額を取得
if len(sys.argv) < 2:
    print("エラー: 目標金額を引数で指定してください(例: python main.py 15000)")
    exit(1)

target = int(sys.argv[1])

# 売上目標の判定
total_amount = 0
for summary in summaries.values():
    total_amount += summary.total_amount

print(f"\n売上合計: {total_amount}円(目標: {target}円)")

if total_amount >= target:
    print("目標達成です!")
elif total_amount >= target * 0.8:
    print(f"目標まであと一歩です(あと{target - total_amount}円)")
else:
    print(f"目標未達成です(あと{target - total_amount}円)")
# ====== ↑ ここまで追加 ======

追加したコードを解説します。

if len(sys.argv) < 2:
    print("エラー: 目標金額を引数で指定してください(例: python main.py 15000)")
    exit(1)

len(sys.argv) < 2で引数が指定されているかを確認しています。sys.argvはコマンドライン引数のリストで、sys.argv[0]にはスクリプト名が入るため、引数が1つも指定されていない場合は長さが1になります。引数がない場合は使い方を表示してexit(1)で終了します。

target = int(sys.argv[1])

引数がある場合はsys.argv[1]で目標金額を取得し、int()で整数に変換しています。コマンドライン引数はすべて文字列として渡されるため、計算に使うには型変換が必要です。

if total_amount >= target:
    print("目標達成です!")
elif total_amount >= target * 0.8:
    print(f"目標まであと一歩です(あと{target - total_amount}円)")
else:
    print(f"目標未達成です(あと{target - total_amount}円)")

売上合計金額に対して、if/elif/elseで3段階の判定を行っています。

条件 メッセージ
目標の100%以上 「目標達成です!」
目標の80%以上100%未満 「目標まであと一歩です(あと○円)」
目標の80%未満 「目標未達成です(あと○円)」

main.pyを実行して動作を確認しましょう。今回から引数に目標金額を指定します。目標金額を15,000円に設定して実行してみてください。

Windowsの場合:

python main.py 15000

Macの場合:

python3 main.py 15000

文房具(6,200円)+ 飲料(7,200円)= 合計13,400円のため、以下のように表示されます。

=== カテゴリ別売上集計 ===
  文房具: 39個 / 6200円
  飲料: 35個 / 7200円

集計結果を summary.json に出力しました

売上合計: 13400円(目標: 15000円)
目標まであと一歩です(あと1600円)

目標金額を変えて、3パターンのメッセージが正しく切り替わることも確認してみましょう。引数を10000にして実行すると以下のように表示されます。

売上合計: 13400円(目標: 10000円)
目標達成です!

引数を20000にして実行すると以下のように表示されます。

売上合計: 13400円(目標: 20000円)
目標未達成です(あと6600円)

コードを変更せずに、実行時の引数を変えるだけで異なる目標金額でテストできることがわかります。

9. エラーハンドリング

実際にツールを使う場面では、CSVファイルが存在しない場合など、想定外の状況が発生することがあります。まず、エラーハンドリングがない状態でどうなるかを確認してみましょう。

9.1 エラーを体験する

Visual Studio Codeのエクスプローラーでsales.csvのファイル名をsales_backup.csvに変更してください。ファイルを右クリックし「名前の変更」を選択すると変更できます。

この状態でmain.pyを実行してみましょう。以下のようなエラーが表示され、プログラムが途中で停止します。

Traceback (most recent call last):
  File "main.py", line 8, in <module>
    with open("sales.csv", "r", encoding="utf-8") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'sales.csv'

これはFileNotFoundErrorというエラーで、指定したファイルが見つからなかったことを示しています。エラーメッセージは英語で表示され、プログラムもその場で停止してしまいます。このままでは、ツールを使うユーザにとって何が起きたのかわかりにくい状態です。また、エラー処理が入っていないと、処理が途中で止まることで中途半端なファイルが作成されるなど、予期しない挙動を引き起こすこともあります。

9.2 try/exceptの追加

前の講座で学んだtry/exceptを使って、このエラーに対処します。main.pyのCSVファイル読み込み部分を以下のように変更してください。

import csv
import json
import sys
from models import Sale, CategorySummary

# CSVファイルの読み込み
sales = []
try:  # ← 追加
    with open("sales.csv", "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            sale = Sale(
                date=row["date"],
                product=row["product"],
                category=row["category"],
                quantity=int(row["quantity"]),
                unit_price=int(row["unit_price"])
            )
            sales.append(sale)
# ====== ↓ ここから追加 ======
except FileNotFoundError:
    print("エラー: ファイル 'sales.csv' が見つかりません。")
    exit(1)

if len(sales) == 0:
    print("エラー: 売上データが0件です。CSVファイルの内容を確認してください。")
    exit(1)
# ====== ↑ ここまで追加 ======

# カテゴリ別の集計
summaries = {}
for sale in sales:
    if sale.category not in summaries:
        summaries[sale.category] = CategorySummary(sale.category)
    summaries[sale.category].add(sale)

# 集計結果の表示
print("=== カテゴリ別売上集計 ===")
for summary in summaries.values():
    print(f"  {summary.category}: {summary.total_quantity}個 / {summary.total_amount}円")

# JSONファイルへの出力
result = []
for summary in summaries.values():
    result.append(summary.to_dict())

with open("summary.json", "w", encoding="utf-8") as f:
    json.dump(result, f, ensure_ascii=False, indent=4)
print("\n集計結果を summary.json に出力しました")

# コマンドライン引数から目標金額を取得
if len(sys.argv) < 2:
    print("エラー: 目標金額を引数で指定してください(例: python main.py 15000)")
    exit(1)

target = int(sys.argv[1])

# 売上目標の判定
total_amount = 0
for summary in summaries.values():
    total_amount += summary.total_amount

print(f"\n売上合計: {total_amount}円(目標: {target}円)")

if total_amount >= target:
    print("目標達成です!")
elif total_amount >= target * 0.8:
    print(f"目標まであと一歩です(あと{target - total_amount}円)")
else:
    print(f"目標未達成です(あと{target - total_amount}円)")

追加したコードを解説します。

try:
    with open("sales.csv", "r", encoding="utf-8") as f:

tryブロックでopen()を囲むことで、ファイルが存在しない場合のエラーに備えています。

except FileNotFoundError:
    print("エラー: ファイル 'sales.csv' が見つかりません。")
    exit(1)

FileNotFoundErrorが発生した場合、日本語のエラーメッセージを表示し、exit(1)でプログラムを終了します。

if len(sales) == 0:
    print("エラー: 売上データが0件です。CSVファイルの内容を確認してください。")
    exit(1)

ファイルが存在してもデータが0件の場合は、その旨を表示して終了します。

9.3 動作確認

ファイル名は先ほどsales_backup.csvに変更したままなので、そのままmain.pyを実行してみましょう。今度は以下のようにわかりやすいメッセージが表示されます。

エラー: ファイル 'sales.csv' が見つかりません。

先ほどの英語のエラーメッセージと比べて、何が起きたかがすぐにわかるようになりました。確認できたら、sales_backup.csvのファイル名をsales.csvに戻してください。

💡 ポイント
try/exceptを使うことで、プログラムがエラーで停止するのを防ぎ、ユーザにわかりやすいメッセージを表示できます。特にファイル操作では、ファイルが存在しないなどのエラーが起こりやすいため、エラーハンドリングを行うことが重要です。

10. まとめ

このハンズオンでは、Pythonを使った売上データの集計の自動化を体験しました。

  • csvモジュールのcsv.DictReader()を使うと、CSVファイルの各行を辞書として読み込める
  • CSVから読み込んだ値はすべて文字列型のため、計算にはint()で型変換が必要
  • クラスを使うことで、データの構造を明確にし、sale.dateのようにわかりやすくアクセスできる
  • add()メソッドで集計処理をクラスに組み込み、データを1件ずつ加算できる
  • to_dict()メソッドでクラスのインスタンスを辞書に変換し、json.dump()でJSONファイルに出力できる
  • sys.argvでコマンドライン引数を受け取り、実行時に目標金額を指定できる
  • if/elif/elseで売上合計と目標金額を比較し、達成状況に応じたメッセージを表示できる
  • try/exceptでファイル操作のエラーに安全に対処できる
  • Pythonの標準ライブラリだけで、実用的なデータ集計ツールを構築できる

11. 次のステップ

🎉 おめでとうございます!Pythonの基本文法から、実用的な自動化ツールの作成まで体験できました。

ここまでで学んだ内容は、Pythonでプログラミングをするうえでの基本中の基本です。ここから先は、より実践的な内容に進みます。

講座名 学べること
データベースとは リレーショナルデータベースの基本概念
Pythonでデータベースを操作しよう PythonからMySQLへの接続とCRUD操作
FastAPI入門 FastAPIの基本とエンドポイントの作成
PythonでREST APIを作ろう FastAPIとSQLModelを使ったREST APIの構築
Pythonで静的解析をしよう Ruff・mypyによるコード品質の向上
Pythonで自動テストをしよう pytestによる自動テストの実践

実務で通用するPython開発スキルを身につけたい方は、ぜひ次のステップに進んでみてください。