パッケージ構成に整理
このレッスンでできるようになること
1本のファイルに詰め込んでいた家計簿を、models と report に分けたパッケージの形に整理できるようになります。
パッケージはフォルダにした module です
第1章で、関数を別ファイルへ移して import することを学びました。ファイルが3本4本と増えてくると、今度はそのファイル自体が散らかります。そこで関係するファイルをフォルダにまとめ、フォルダごと import できるようにしたものがパッケージです。
図書のデータと検索を分ける例で、配置と読み込みを対応させます。
library/
__init__.py
books.py
search.py__init__.py はパッケージの目印です。中身は空で構いません。
Python
from library.books import Book
from library.search import count_books, total_pageslibrary.books の . は、フォルダの中のファイルを指します。同じファイルから複数の名前を読み込むときは、カンマで並べます。演習でも、クラスを置くファイルと集計関数を置くファイルを分けて読み込みます。
どう分けるかは責務で決めます
ファイル名で迷ったら、そのコードが何の役目を持っているかで分けます。今回の家計簿なら次の2つです。
models.py— データの形。第7章で作ったExpenseクラスを置きますreport.py— データを読んで数える処理。合計やカテゴリ別集計を置きます
この分け方の良いところは、Expense に項目を1つ足したくなったときに models.py だけを開けばよく、集計の書き方を変えたくなったときは report.py だけを開けばよいことです。逆に「表示の関数と集計の関数が同じファイルに混ざっている」と、直したい場所を探すところから始まります。
この演習では中身をコードで書き出します
ふだんはエディタで models.py を作りますが、この学習環境ではファイルを1本しか書けません。そこで中身を文字列として持っておき、プログラムからファイルに書き出してから import します。
次は図書の部品をファイルに配置する例です。
Python
from pathlib import Path
BOOKS_SRC = """from dataclasses import dataclass
@dataclass
class Book:
title: str
pages: int
"""
SEARCH_SRC = """def count_books(books):
return len(books)
def total_pages(books):
return sum([book.pages for book in books])
"""
folder = Path("library")
folder.mkdir(exist_ok=True)
(folder / "__init__.py").write_text("", encoding="utf-8")
(folder / "books.py").write_text(BOOKS_SRC, encoding="utf-8")
(folder / "search.py").write_text(SEARCH_SRC, encoding="utf-8")三重引用符の文字列は改行を含めてコードを保持します。folder / "books.py" はフォルダ内のファイルのパスです。文字列の中身はまだ実行されず、書き出したファイルを import したときに定義されます。
mkdir(exist_ok=True) は、すでにフォルダがあってもエラーにしない書き方です。write_text は第6章の pathlib で出てきたものです。
書き出した直後に import するときは、1行だけ添えるものがあります。
Python
import importlib
importlib.invalidate_caches()Python は「どこにどのファイルがあるか」を一度調べたら覚えています。プログラムの実行中に新しく作ったフォルダは、その記憶に入っていません。invalidate_caches() はその記憶を捨てて、もう一度探し直させる命令です。作成直後でも見つかることはありますが、探索の記憶が残っている場合に備えて呼んでおきます。
演習ではクラス・集計関数の中身を完成済みの文字列として渡します。担当するのは3ファイルへの配置と2行の import です。読み込み前の名前は None にしてあり、Expense が読み込まれるまで表示を行わないため、ひな形のままでも実行できます。
要件
- 完成済みの package.mkdir で作る kakeibo フォルダに、init.py / models.py / report.py の3本を書き出す
- init.py には空文字列、models.py には MODELS_SRC、report.py には REPORT_SRC を write_text で書く。文字コードは utf-8 とする
- 完成済みの importlib.invalidate_caches() の後で、kakeibo.models から Expense を import する
- kakeibo.report から total_amount と category_totals を1行で import する
- クラスと集計関数の文字列、支出データ、表示はそのまま使う。Expense が None の間は表示を行わない
- 完成時は1行目が 合計 6130円、2〜4行目がカテゴリ名の昇順でカテゴリ別合計、5行目が 高額 2件 になる