1ファイルの集約コードはもう卒業!モジュール分割に挑戦 カプセル化の入口

MicroPython

ファイルに1つ全部書くのは卒業しよう

Raspberry Pi PicoでLチカやPWMを試していると、main.pyがだんだん長くなってきませんか。

最初はLEDを光らせるだけなので1ファイルで十分です。

でも、機能を足したりコメントを増やしたりすると、「どこがメインでどこがLEDのロジックなのか分かりにくい…」となりがちです。

私も最初は1つのmain.pyに全部書いていて、後から読み返すと自分でも迷子になりました。

そこで試したのが、コードを2つ(複数)のモジュールに分割する方法です。

今回は、LEDの明るさを滑らかに変えるMicroPythonのコードを例に、main.pyled.pyに分ける手順を解説します。

Raspberry Pi Pico / Pico2 どちらでも使える考え方なので、電子工作・プログラミング初心者の方にもきっと役立つはずです。

ピコさん
ピコさん
ベースとなるコードは、まずは下の記事を見てね。ここから LED の機能だけを別ファイルに抜き出してみるよ。

今回のゴールと完成イメージ

この記事のゴールは、もともと1つだったLEDフェード用のコードを、役割ごとに2つのモジュールへ分けることです。

  • main.py:ハードウェア初期化とメインループを担当
  • led.py:LEDの明るさ計算やフェード処理などのロジックを担当
  • from led import fade  だけを外から使う入口にして、モジュールのカプセル化(情報隠蔽)を体験する

完成すると、main.pyの中身はとてもスッキリします。

「どのピンを使うのか」「どのくらいのスピードでフェードさせるのか」が一目で分かる構造になります。

一方で、led.pyにはLED制御の関数と、関数の説明コメントをまとめておきます。外からは fade() ひとつだけを入口として見せておき、内部でこっそり set_brightness() を使うようにすることで、ロジックの変更に強いコードになります。

将来、別のプロジェクトで同じようなLEDフェードを使いたくなったときも、led.pyだけコピーすれば簡単に再利用できますし、main.py側の修正も最小限で済みます。

やってみた感じとしては、「思ったより変更点が少なくて、読むのがかなりラクになった」という印象でした。

まずは元の1ファイル版コードを確認しよう

最初に、1つのファイルだけでLEDをフェードさせるコードをおさらいしておきます。

このコードが少し長くなってきたので、「そろそろ分けたいな」というところからスタートです。

ピコさん
ピコさん
今回のコードくらいなら、まだ1ファイルのままでも困らないんだけど、モジュールごとにファイルを分ける練習だと思って読んでみてね。

このままでも十分動きます。

ただ、関数や定数が増えてくると、1ファイルに詰め込むのはつらくなってきます。

次の章では、「どこでファイルを分けるのが良いか」という考え方を整理してみましょう。

モジュール分割の考え方:役割ごとに切り分ける

コードを2つのモジュールに分けるときのコツは、「役割」を意識することです。

Raspberry Pi PicoでのLED制御なら、ざっくり次のように分けられます。

  • ハードウェア依存の部分(ピン番号、PWM周波数、メインループ)
  • LEDの明るさ計算やフェード処理といったロジック部分

この2つを別ファイルにすることで、どこを読めば何が分かるのかがハッキリします。

もうひとつのポイントが、関数の説明テンプレートをコードの近くに置くことです。

しっかりコメントを書いておくと、あとから見返したときの理解スピードが全然違います。

特に初心者にとっては、「この関数は何をするのか」「どんな引数を渡せばいいのか」が一目で分かるのは大きなメリットです。

led.pyを作る:LED制御ロジック専用モジュール

それでは、LED制御ロジックをまとめたled.pyを作っていきます。

ここには、PWMのデューティ計算やフェード処理など、「どのプロジェクトでも使い回せそうな部分」を集めます。

led.py

from time import sleep

# ==========================
# 定数定義
# ==========================
MAX_DUTY = 65535  # 16bitスケールの最大値


# ===================================================
# 関数名:
#   set_brightness
#
# 役割:
#   PWMにデューティ比を設定してLEDの明るさを変える
#
# 引数:
#   <PWM> pwm / LEDを接続したPWMオブジェクト
#   <int> percent / 明るさを0〜100のパーセンテージで指定
#
# 戻り値:
#   なし / pwmの出力を更新するだけ
#
# ローカル変数:
#   ratio: percentを0.0〜1.0に変換した比率
#   duty_value: duty_u16に渡す0〜MAX_DUTYの整数値
#
# 使用する定数:
#   MAX_DUTY: デューティ比の最大値(16bitスケール)
#
# グローバル変数:
#   なし
#
# 注意点:
#   percentは0〜100を想定する(範囲外を渡すと不自然な明るさになる)
# ===================================================
def set_brightness(pwm, percent):
    ratio = percent / 100.0
    duty_value = int(MAX_DUTY * ratio)
    pwm.duty_u16(duty_value)


# ===================================================
# 関数名:
#   fade
#
# 役割:
#   明るさをstart_percentからend_percentまで、指定ステップ数で変化させる
#
# 引数:
#   <PWM> pwm / LED用PWMオブジェクト
#   <int> start_percent / フェード開始時の明るさ(0〜100)
#   <int> end_percent   / フェード終了時の明るさ(0〜100)
#   <int> steps         / 何段階に分けて変化させるか
#   <float> step_delay  / 各ステップ間の待ち時間(秒)
#
# 戻り値:
#   なし / ループの間にPWMを更新し続ける
#
# ローカル変数:
#   step_size: 1ステップごとに増減させる明るさの量
#   current  : 現在の明るさ(パーセンテージ)
#
# 使用する定数:
#   なし(最大・最小は呼び出し側のpercentで管理)
#
# グローバル変数:
#   なし
#
# 注意点:
#   stepsは1以上を渡すこと(0だとゼロ除算になる)
# ===================================================
def fade(pwm, start_percent, end_percent, steps, step_delay):
    step_size = (end_percent - start_percent) / steps
    current = start_percent

    for _ in range(steps + 1):
        set_brightness(pwm, current)
        sleep(step_delay)
        current += step_size

これでled.pyは、LED制御のロジックだけを持つシンプルなモジュールになりました。

ピコさん
ピコさん
main関数がないから このコード単体だけでは動かないよ。呼び出されること前提としたモジュールなんだ。

main.pyを作る:ハード初期化とメインループだけに整理

次はmain.pyです。

こちらは、Raspberry Pi Pico特有の「どのピンを使うか」「どのくらいの速さでフェードさせるか」といった設定と、メインループだけに絞ります。

main.py


from machine import Pin, PWM
from time import sleep
from led import fade

# ==========================
# 定数定義
# ==========================
LED_PIN = 15               # LED接続ピン
PWM_FREQ_HZ = 1000         # PWM周波数(Hz)

MIN_PERCENT = 0            # 最小の明るさ(0%)
MAX_PERCENT = 100          # 最大の明るさ(100%)

FADE_STEPS = 100           # フェードの分割数(多いほどなめらか)
STEP_SECONDS = 0.01        # 1ステップごとの待ち時間(秒)

HOLD_TOP_SECONDS = 0.5     # 最大輝度でキープする時間(秒)
HOLD_BOTTOM_SECONDS = 0.5  # 最小輝度でキープする時間(秒)


# ==========================
# メイン処理
# ==========================
def main():
    pwm = PWM(Pin(LED_PIN))
    pwm.freq(PWM_FREQ_HZ)

    while True:
        # フェードイン(暗い → 明るい)
        fade(
            pwm,
            MIN_PERCENT,
            MAX_PERCENT,
            FADE_STEPS,
            STEP_SECONDS,
        )
        sleep(HOLD_TOP_SECONDS)

        # フェードアウト(明るい → 暗い)
        fade(
            pwm,
            MAX_PERCENT,
            MIN_PERCENT,
            FADE_STEPS,
            STEP_SECONDS,
        )
        sleep(HOLD_BOTTOM_SECONDS)


# ==========================
# 実行部
# ==========================
if __name__ == "__main__":
    main()

見ての通り、main.pyはかなりスッキリしました。

「ピン番号やフェードのスピードを変えたいときはここだけ見ればいい」というのがとても快適です。

Raspberry Pi Pico / Pico2を切り替えたり、別のプロジェクトにコピーしたりするのも簡単になります。

ピコさん
ピコさん
大きなプロジェクトでは、モジュールごとに担当やチームを決めて開発するよ

なぜ「from led import fade」だけにしているの? (カプセル化)

今回のサンプルでは、from led import fade と書いて、あえて set_brightness 関数はインポートしていません。

理由はシンプルで、「main.py側から使える入口(LED制御の窓口)は fade だけ」にしておきたいからです。

set_brightness は、あくまで fade の中だけで使う「裏方の関数」です。main.pyから直接呼べてしまうと、

  • どの場面で set_brightness を使うべきか迷う
  • コードを読む人が「fade と set_brightness のどっちを使えばいいの?」と悩む
  • あとから set_brightness の仕様を変えたくなったとき、影響範囲が広がりやすい

そこで、「LEDの明るさをゆっくり変える」という外から見える機能は fade ひとつにまとめてしまい、その中でこっそり set_brightness を呼ぶ形にしています。

こうすると、main.pyは「fadeを呼べばLEDがいい感じにフェードしてくれる」ことだけ知っていればOK になります。LEDの明るさをどう計算しているか、細かい中身まで知る必要はありません。

このように、中の仕組みはモジュール側に閉じ込めて、外から見える入り口を少なくしておく考え方を、プログラミングの世界では「カプセル化」や「情報隠蔽」と呼びます。

Pythonには「private」「public」といったキーワードはありませんが、今回のように「どの関数を外から使わせるか」を自分で決めておくことで、コードが壊れにくく、読みやすくなっていきます。

ピコさん
ピコさん
「外から使う関数はどれにするか?」を先に決めておくと、モジュール設計がグッとやりやすくなるよ。fade が“お客様入口”、set_brightness は“バックヤード”みたいなイメージだね。

実行と動作チェック:2ファイルでもちゃんと動く?

ここまで来たら、あとはいつも通り実行してみるだけです。

手順はシンプルで、Raspberry Pi Picoのストレージにmain.pyとled.pyの2つを保存するだけです。

  • Thonnyでled.pyを作成して保存
  • 同じくmain.pyを作成して保存
  • main.pyを実行する

LEDが今までどおり、暗いところから明るくなり、また暗くなる動きを繰り返せばOKです。

もしエラーが出た場合は、import文のスペルやファイル名が正しいかどうかを確認してみてください。

特に「from led import fade」のledとファイル名led.pyが一致しているかがポイントです。

ピコさん
ピコさん
led.py を PC 側だけに保存していても動かないよ。必ず Raspberry Pi Pico のストレージに保存してね。私も最初それに気づかなくて、30分くらい試行錯誤しちゃった…。

main.py と led.py は、必ず同じフォルダ(Raspberry Pi Pico のストレージ直下など)に保存してください。フォルダが分かれていると from led import fade が見つからず、ImportError になってしまいます。

まとめ:モジュール分割でコードはグッと読みやすくなる

今回は、Raspberry Pi PicoのLEDフェードコードを題材に、1つのmain.pyを2つのモジュールに分割する手順を紹介しました。

  • 元のコードは1ファイルでも動くが、長くなると読みづらい
  • LED制御ロジックをled.pyに切り出すと再利用しやすくなる
  • main.pyはハード初期化とメインループだけに絞るとスッキリする
  • 関数テンプレートをコメントとして入れておくと、自分にも読者にも親切

モジュール分割は、最初は少しハードルが高く感じるかもしれません。

でも、一度やり方をつかんでしまえば、MicroPythonでもPythonでも同じ考え方でどんどん応用できます。

もし次の一歩で迷ったら、同じ要領で「ボタン入力用のモジュール」や「センサー読み取り用のモジュール」を作ってみるのもおすすめです。

Raspberry Pi Pico / Pico2での電子工作が、きっともっと楽しく、整理されたものになるはずです。

コメント