おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

Python初級者のお勉強ノート(22)パッケージの作成

Pythonでプログラムを作成していると、ファイルが増えてくることがあります。 その際、単一ファイルのスクリプトとして管理するのは難しくなります。 さらに、他のプロジェクトで再利用する場合や、チームで開発する場合には、構造的に整理された形式が必要です。 そこで登場するのが「パッケージ化」です。 本記事では、パッケージ化の目的や基本的な構成、分割の理由、そしてセットアップファイル setup.py の役割について詳しく解説します。

パッケージ化の目的

パッケージ化を行うことで、次のような利点を得られます。

構造化

パッケージ化することで、コードをモジュール単位で整理できます。例えば、データ処理出力処理が混在しているコードを、役割ごとにファイルとして分割できます。これにより、コードの可読性が向上します。

再利用性

一度パッケージ化すれば、同じコードを他のプロジェクトで簡単に再利用できます。特に、頻繁に使用する汎用的な機能は、パッケージ化しておくと便利です。

配布の容易さ

Pythonでは、パッケージ化したコードをPyPIPython Package Index)などを通じて配布できます。この手順は今回の解説には含まれませんが、パッケージ化することで配布の準備が整います。

メンテナンスの容易化

パッケージ化されたコードはモジュールごとに独立しているため、個々の修正や機能追加が簡単です。

パッケージの基本構成

Pythonのパッケージはディレクトリ構造を持つフォルダであり、通常以下のようなファイルやディレクトリが含まれます。

mypackage/
├── mymodule/
│   ├── __init__.py
│   ├── main.py
│   ├── utils.py
├── tests/
│   ├── test_module.py
├── setup.py
└── README.md

各ファイル・ディレクトリの役割

  1. mymodule/ パッケージの中心となるディレクトリで、実際のコードが含まれます。このディレクトリ名がパッケージ名になります。
  2. __init__.py パッケージをモジュールとして認識させるためのファイルです。内容は空でも構いません。
  3. main.py プログラムのメイン部分を記述します。今回は、コマンドライン引数を処理する部分が該当します。
  4. utils.py 補助的な機能や再利用可能な関数をまとめたファイルです。今回は、スケジュールのデータ読み書きや基本操作を担当します。
  5. tests/ テストコードを格納するディレクトリです。テストフレームワーク(例: pytest)を使って、パッケージ全体の動作確認を行います。
  6. setup.py パッケージのインストール設定を記述するファイルです。パッケージの依存関係やエントリーポイント(後述)もここで指定します。
  7. README.md パッケージの説明を記述するファイルです。主に配布や利用者向けのドキュメントとして使用されます。

コードを分割する理由

なぜmain.pyutils.pyに分けるのか?

コードを分割する理由は以下の通りです。

  1. 役割ごとの分離
    • main.py はエントリーポイント(実行時に最初に呼び出される部分)としての役割を担います。
    • utils.py はスケジュールデータの操作や補助的な関数を担当します。 この分離により、コードが見やすくなるだけでなく、テストしやすくなります。
  2. 再利用性の向上 utils.py に分けることで、他のプログラムやスクリプトからも簡単に関数を呼び出せます。例えば、スケジュールデータを操作する関数だけを別のツールで利用する場合にも便利です。
  3. 変更の影響を限定 特定の機能を修正する場合、該当するファイルだけを変更すればよく、他の部分に影響を与えません。

setup.pyの詳細解説

setup.py は、パッケージの設定やインストール方法を記述する重要なファイルです。以下に典型的な例を示します。

from setuptools import setup, find_packages

setup(
    name="sch",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[],
    entry_points={
        "console_scripts": [
            "sch=sch.main:main",
        ],
    },
    description="A simple schedule management tool",
    author="Your Name",
    author_email="your_email@example.com",
    url="https://example.com/sch",
)

各項目の説明

  1. name パッケージ名を指定します。他のパッケージと重複しない名前を選びます。
  2. version パッケージのバージョン番号を指定します。通常は MAJOR.MINOR.PATCH の形式を使用します。
  3. packages パッケージとしてインストールするディレクトリを指定します。find_packages() を使うと、自動的にパッケージを検出します。
  4. install_requires パッケージが依存する他のPythonパッケージを指定します(例: ["requests", "numpy"])。
  5. entry_points エントリーポイントは、パッケージの実行方法を指定します。
    • "console_scripts": コマンドラインから直接プログラムを実行できるようにする設定。
    • 例: sch=sch.main:main
      • sch: コマンド名。pip install 後に使用可能。
      • sch.main: パッケージ内のモジュール名。
      • main: 実行される関数名。
  6. description パッケージの簡単な説明文です。
  7. authorauthor_email 作成者の名前と連絡先を記載します。

ここまでで、パッケージ化の目的、基本構成、コードの分割理由、そしてsetup.pyの詳細について解説しました。 ここからは、具体的にsch.pyをパッケージ化する手順を説明します。

sch.pyをパッケージ化する手順

ディレクトリ構造を準備

以下のようなディレクトリ構造を作成します。

sch_package/
├── sch/
│   ├── __init__.py
│   ├── main.py
│   ├── utils.py
├── tests/
│   ├── test_sch.py
├── setup.py
└── README.md
  • sch/: 実際のコードを格納するディレクトリ。
  • __init__.py: パッケージとして認識させるためのファイル(空でOK)。
  • main.py: スケジュール管理ツールのエントリーポイント。
  • utils.py: スケジュールの読み書きや操作を行う補助機能。
  • tests/: テスト用コードを格納するディレクトリ。
  • setup.py: パッケージのインストール設定。
  • README.md: パッケージの説明。

sch.pyを分割

sch.pyのコードを以下のように分割します。

main.py
コマンドライン引数を処理し、適切な操作を呼び出す部分を担当します。

import argparse
from .utils import add_schedule, list_schedule, delete_schedule

def main():
    """コマンドライン引数を解析し、処理を分岐します"""
    parser = argparse.ArgumentParser(description="スケジュール管理プログラム")
    parser.add_argument("-a", nargs=3, metavar=("DATE", "TIME", "TITLE"), help="スケジュールを追加します")
    parser.add_argument("-l", metavar="DATE", help="スケジュールを一覧表示します")
    parser.add_argument("-d", type=int, metavar="ID", help="スケジュールを削除します")
    args = parser.parse_args()
    if args.a:
        add_schedule(args.a[0], args.a[1], args.a[2])
    elif args.l:
        list_schedule(args.l)
    elif args.d:
        delete_schedule(args.d)
    else:
        parser.print_help()

インポート先のファイルが.utilsとなっています。 この形を相対インポートといいます。

  • . は現在のパッケージを意味します(カレントディレクトリ)。
  • .. は親ディレクトリ(1階層上)を意味します。
  • ... はさらにその親ディレクトリ(2階層上)を意味します。

例えば、from .utils という記述は「現在のディレクトリ内にある utils モジュール」をインポートすることを表します。

絶対インポートという方法もあります。 絶対インポートはプロジェクトのルートディレクトリを基準に、完全なパスを使ってモジュールを指定します。

例:

from sch.utils import function_name

ここでは sch がプロジェクトのルートディレクトリ直下にあるディレクトリです。 ドットはディレクトリの区切子(デリミタ)を表すので、sch.utilsschディレクトリ下のutilsファイルの意味になります。

utils.py
スケジュールデータの保存・読み取りと操作を行う補助関数をまとめます。

import json
import os

DATA_FILE = "schedule.json"

def load_data():
    """スケジュールデータをロードします"""
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE, "r") as f:
            return json.load(f)
    return []

def save_data(data):
    """スケジュールデータを保存します"""
    with open(DATA_FILE, "w") as f:
        json.dump(data, f, indent=4)

def add_schedule(date, time, title):
    """スケジュールを追加します"""
    d = load_data()
    new_id = d[-1]["id"] + 1 if d else 1
    s = {"id": new_id, "date": date, "time": time, "title": title}
    d.append(s)
    save_data(d)
    print(f"スケジュールを追加しました: {date} {time} - {title}")

def list_schedule(date):
    """指定された日付のスケジュールを一覧表示します"""
    d = load_data()
    l = [s for s in d if s["date"] == date]
    if l:
        print(f"スケジュール一覧 ({date}):")
        for s in l:
            print(f"{s['id']}. {s['time']} - {s['title']}")
    else:
        print(f"{date}のスケジュールはありません。")

def delete_schedule(schedule_id):
    """指定されたIDのスケジュールを削除します"""
    d = load_data()
    d = [s for s in d if s["id"] != schedule_id]
    save_data(d)
    print(f"スケジュール(ID: {schedule_id})を削除しました。")

setup.pyを作成

setup.pyを以下のように記述します。

from setuptools import setup, find_packages

setup(
    name="sch",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[],
    entry_points={
        "console_scripts": [
            "sch=sch.main:main",
        ],
    },
    description="A simple schedule management tool",
    author="Your Name",
    author_email="your_email@example.com",
    url="https://example.com/sch",
)

author, author_email, urlのところは、実際にはプログラムの作者の氏名、メールアドレス、ウェブサイトアドレスを入れます。

テストコードを配置

tests/test_sch.pyに既存のテストコードを移動します。 ファイルを分割したのでコードの修正が必要です。

import pytest
import os
from sch import utils

TEST_DATA_FILE = "test_schedule.json"

# テスト用のデータファイルを一時的に使用するように変更
@pytest.fixture(autouse=True)
def setup_and_teardown():
    utils.DATA_FILE = TEST_DATA_FILE
    yield
    if os.path.exists(TEST_DATA_FILE):
        os.remove(TEST_DATA_FILE)

def test_add_schedule():
    """スケジュールの追加をテスト"""
    utils.add_schedule("2024-12-10", "10:00", "Meeting")
    utils.add_schedule("2024-12-10", "14:00", "Lunch")
    data = utils.load_data()
    assert len(data) == 2
    assert data[0]["title"] == "Meeting"
    assert data[1]["title"] == "Lunch"

def test_list_schedule(capsys):
    """スケジュールの一覧表示をテスト"""
    utils.add_schedule("2024-12-10", "10:00", "Meeting")
    utils.add_schedule("2024-12-10", "14:00", "Lunch")
    utils.list_schedule("2024-12-10")
    captured = capsys.readouterr()
    assert "Meeting" in captured.out
    assert "Lunch" in captured.out

def test_delete_schedule():
    """スケジュールの削除をテスト"""
    utils.add_schedule("2024-12-10", "10:00", "Meeting")
    utils.add_schedule("2024-12-10", "14:00", "Lunch")
    utils.delete_schedule(1)
    data = utils.load_data()
    assert len(data) == 1
    assert data[0]["title"] == "Lunch"

パッケージ化したので、以前のように単にpytestとコマンドラインから打ち込むのでは、テストは上手くいきません。 テストはプロジェクトのルートディレクトリから、次のように行います。

PS >py -m pytest tests

テスト後にキャッシュのディレクトリ(.pytest_cacheなど)が残りますが、これは削除しても残しておいても構いません。 このような一時ファイルは、後に説明する.gitignoreでバージョン管理の対象から外すことで解決するのが一番良い方法です。 今回はそこまでは触れません。

インストールして実行

パッケージ化が完了したら、次の手順でインストールします。

  1. ターミナルでルートディレクトリに移動。
  2. コマンドを実行してパッケージをインストール。
pip install .

引数にドット(カレントディレクトリ)があることに注意してください。 パッケージの作成でもbuildなどのディレクトリが作られます。 これらも.gitignoreで対処するのが良いのですが、説明は別の記事で行い、ここでは触れません。 それらのディレクトリを削除しても良いですが、残しておいても構いません。

  1. schコマンドを実行してスケジュール管理ツールを利用できます。
PS > sch -a "2024-12-12" "10:00" "Team Meeting"
PS > sch -l "2024-12-12"
PS > sch -d 1

これでsch.pyをパッケージ化する手順は完了です。 プロジェクトにおいては、常にパッケージ化を念頭において開発することが望まれます。

Python初級者のお勉強ノート(21)Pythonにおけるテスト

プログラム開発において、コンピュータによるテストは欠かせません。 特に複雑なロジックや外部依存が絡む場合、人間の目でバグを発見するのは難しいものです。 それに対して、コンピュータによるテストは高速で正確であり、複雑なプログラムにも対処できます。 本記事では、Pythonでのテストの基本から、実際のテストプログラム例までを解説します。

テストとは何か

テストとは、プログラムが正しく動作していることを確認するために実行される一連の手順です。以下のような目的があります:

  • バグの早期発見: プログラムの想定外の動作を発見する。
  • 変更による影響の確認: コードの変更が既存の機能に悪影響を与えていないことを確認する。
  • コードの信頼性向上: プログラムが設計通りに動作することを保証する。

また、「テスト駆動開発(TDD: Test-Driven Development)」「振る舞い駆動開発(BDD: Behavior-Driven Development)」と呼ばれる、プログラム開発をテストから始める手法もあります。 特に、TDDの進化形である「振る舞い駆動開発」は、ユーザーの視点でシステムの振る舞いを定義し、その振る舞いをテストで確認する開発手法で、さまざまな利点があります。

  • ユーザーの要求に対するプログラムの振る舞いに焦点があたっている。
  • その振る舞いをテストで検証。
  • プログラムを知らないクライアントにも「振る舞い」は理解しやすいため、開発者とクライアントのコミュニケーションがとりやすい。

ここではTDDやBDDまでには踏み込まないので、必要な方はそれぞれの専門書やウェブサイトを参照してください。

テストには以下の種類があります:

  • ユニットテスト: 個々の関数やメソッドの動作を確認するテスト。
  • 統合テスト: 複数のモジュールが正しく連携することを確認するテスト。
  • システムテスト: システム全体が正しく動作することを確認するテスト。

ここではユニットテストにポイントを絞って解説します。

Pythonで用いられる代表的なテストモジュール

Pythonにはテストを支援するためのモジュールがいくつか用意されています。以下は代表的なものです:

  • unittest:

  • pytest:

  • doctest:

    • ドキュメント内のコード例をそのままテストとして実行。
    • 主にサンプルコードの検証に使用。

ここではpytestを説明します。

pytestディレクトリ構成

プロジェクトを以下のような構成にすると、pytestがテストスクリプトを自動検出します。

my_project/
├── sch.py          # テスト対象のプログラム
├── tests/
│   ├── test_sch.py # テストスクリプト
│   └── __init__.py # 空でも良い

テストはtests/ディレクトリに配置し、test_で始まるファイル名にします。

pytest でよく使われるテスト手法(例)

以下の手法は後で紹介するsch.pyのテストプログラムでも使われています。 詳しい説明はプログラムを示した後の解説で行います。

  • フィクスチャ

    • テストに必要な環境(データベース接続、一時ファイルなど)を準備・後片付けする仕組み
    • @pytest.fixtureで定義し、テスト関数に注入できる
  • モック

    • 外部依存(API、DBアクセス、ファイル操作など)を模倣するオブジェクトを用意
    • 実際のリソースを操作せず、想定通りの振る舞いを再現してテストできる
  • 標準出力のキャプチャ

    • capsysフィクスチャを使い、テスト時のprint出力を記録・検証
    • コマンドライン出力が期待通りか簡潔に確認可能

sch.pyのテストプログラム

テストプログラムは以下の機能をテストしています:

  • スケジュールの追加: 新しいスケジュールが正しく保存されるか。
  • スケジュールの一覧表示: 指定した日付のスケジュールが正しく表示されるか。
  • スケジュールの削除: 指定したIDのスケジュールが正しく削除されるか。
import pytest
import sch
import os
import json

TEST_DATA_FILE = "test_schedule.json"

# テスト用のデータファイルを一時的に使用するように変更
@pytest.fixture(autouse=True)
def setup_and_teardown():
    sch.DATA_FILE = TEST_DATA_FILE
    yield
    if os.path.exists(TEST_DATA_FILE):
        os.remove(TEST_DATA_FILE)

def test_add_schedule():
    """スケジュールの追加をテスト"""
    sch.add_schedule("2024-12-10", "10:00", "Meeting")
    sch.add_schedule("2024-12-10", "14:00", "Lunch")
    data = sch.load_data()
    assert len(data) == 2
    assert data[0]["title"] == "Meeting"
    assert data[1]["title"] == "Lunch"

def test_list_schedule(capsys):
    """スケジュールの一覧表示をテスト"""
    sch.add_schedule("2024-12-10", "10:00", "Meeting")
    sch.add_schedule("2024-12-10", "14:00", "Lunch")
    sch.list_schedule("2024-12-10")
    captured = capsys.readouterr()
    assert "Meeting" in captured.out
    assert "Lunch" in captured.out

def test_delete_schedule():
    """スケジュールの削除をテスト"""
    sch.add_schedule("2024-12-10", "10:00", "Meeting")
    sch.add_schedule("2024-12-10", "14:00", "Lunch")
    sch.delete_schedule(1)
    data = sch.load_data()
    assert len(data) == 1
    assert data[0]["title"] == "Lunch"

テストプログラムの解説

  • フィクスチャ:

    • @pytest.fixturePythonのデコレータという機能。デコレータについての説明はここでは省略。別の記事で扱う予定。
    • @pytest.fixtureの次の行に関数(フィクスチャ)を書く。この関数には、テストに必要な環境(データベース接続、一時ファイルなど)を準備・後片付けする仕組みを記述する。
    • セット・アップはyeildの前に書く。yeild自体についての説明はここでは省略。別の記事で扱う予定
    • テスト終了後のティアダウン(クリーンアップともいう)はyeildの後に書く。
    • セットアップでは、sch.pyの定数DATA_FILEをテスト用のファイル名に変更している(sch.pyはインポート時に実行され、DATA_FILE=schedule.jsonになっている。これがセットアップで書き換えられる)。Pythonの定数は実は変数なので書き換えが可能であることに注意。
    • ティアダウンでは、テスト用のファイルが存在したら、それを消去しておく。
  • テストはtest_XXXXの関数が行う

    • ここには3つのテストの関数がある。これらの関数は、特に設定をしなければ、上の関数から順に実行される。
    • 各テスト関数は、フィクスチャのセットアップ後に実行され、実行後はフィクスチャのティアダウンが実行される。
    • それぞれのテストでは以下にのべるアサーションやキャプチャを使う。
  • アサーションを用いた検証:

    • assert文を使って期待する結果と実際の出力を比較する。期待と異なる結果を「失敗(failure)」という。失敗の場合はその結果を画面出力する。
    • assert文は「assert 条件式, [エラーメッセージ]」の形で記述する。
    • 条件がTrueであれば、何も起こらず次のコードが実行され、Falseの場合はAssertionErrorが発生する。エラーメッセージ(オプション)を指定すると、エラー時にメッセージを表示できる。
    • エラーはpytestがキャッチしているため、プロセスが止まることはない。またテストはそれぞれ独立に行われるので、あるテストでエラーが発生しても他のテストに影響はない。
  • 標準出力のキャプチャ:

    • capsysは、(1)テスト前に標準出力(stdout)や標準エラー出力(stderr)を画面などの外部への出力からpytestの内部に取り込む(キャプチャする)ように変更、(2)テスト実行時にそのキャプチャデータを利用、(3)テスト後は出力を元にもどす、ということをする。したがって、これはpytestの提供する一種のフィクスチャである。
    • capsysを使うためにはまず、テスト関数の引数にcapsysを入れる。
    • 出力をキャプチャするにはcapsys.readouterr関数を使う。

テストの実行

  • pytestをインストール: pipを用いる。
PS > pip install pytest
PS > pytest
================================================= test session starts =================================================
platform win32 -- Python 3.13.0, pytest-8.3.4, pluggy-1.5.0
rootdir: C:\Users\(ユーザ名)\Documents\sch
collected 3 items

tests\test_sch.py ...                                                                                            [100%]

================================================== 3 passed in 0.07s ==================================================
PS >
  • 実行結果を確認
    • 3 passed in 0.07sと出力されたので、3つのテスト関数が成功したことがわかる。

まとめ

本記事では、以下の内容を学びました:

  1. テストの基本概念と種類。
  2. Pythonで利用可能な代表的なテストモジュール。
  3. pytestの使い方。
  4. テスト手法(フィクスチャ、出力キャプチャ)とその応用。

pytestを使うことで、テストの記述や実行がシンプルになります。 テストを書くことは難しいことではありません。 プログラムを作成するときは、かならずテストも書くようにしてください。

Python初級者のお勉強ノート(20)コマンドラインから起動するプログラムの作成

Pythonを使って、コマンドライン上で動作するスケジュール管理プログラムを作成します。 このプログラムでは以下の機能を実装します:

  • スケジュールの追加
  • スケジュールの表示
  • スケジュールの削除

プログラム名はsch.py(schはscheduleの最初の3文字)で、コマンドラインからシンプルに操作できるよう設計します。

スケジュール管理プログラムの設計

今回作成するプログラムの特徴は以下の通りです:

  1. コマンドライン引数で操作:
    • -a: スケジュールの追加
    • -l: スケジュールの一覧表示
    • -d: スケジュールの削除
  2. データの保存と読み込み:
    • JSONファイルを利用します。
  3. シンプルな構造:
    • すべての関数は簡潔で明確な役割を持たせます。

argparseモジュールとは?

argparseモジュールは、Pythonコマンドライン引数を解析するための標準ライブラリです。 このモジュールを使うと、プログラムをコマンドラインから柔軟に操作できるようになります。

argparseの基本的な使い方

1. 引数パーサーを作成する

argparse.ArgumentParserを使って、引数を処理するための「引数パーサー」を作成します。 引数パーサーは、ArgumentParserクラスのインスタンスです。 このパーサーは、コマンドライン引数を解釈し、プログラムで使いやすい形に変換する役割を持っています。

import argparse

parser = argparse.ArgumentParser(description="コマンドライン引数の例")
  • description引数: プログラムの概要を説明します。この説明は、ユーザーが--helpを指定したときに表示されます。 この引数の与え方をキーワード引数といいます。 キーワード引数についてはPython初心者のお勉強ノート(10)関数の基礎を参照してください。

例えば、このコードを含むスクリプトpython script.py --helpで実行すると、以下のようなヘルプメッセージが表示されます:

usage: script.py [-h]

コマンドライン引数の例
2. 引数を定義する

次に、プログラムが受け取る引数を定義します。「引数パーサー」のadd_argumentメソッドを使って、引数の名前や動作を指定します。

parser.add_argument("-a", help="スケジュールを追加する")
  • -a:
    • コマンドライン引数のショートスタイル(短い名前)。
    • 実際には、ユーザーが-aに続けて値を指定します(例: -a value)。
  • help:
    • 引数の説明です。--helpを実行したときに、この引数がどのような目的で使われるのか表示されます。
3. 引数を解析する

最後に、コマンドラインから渡された引数を解析し、その値を取得します。これにはparse_argsメソッドを使用します。

args = parser.parse_args()
  • argsは、ユーザーが指定した引数を保持するオブジェクトです。
  • 属性として各引数にアクセスできます(例: args.a)。

例えば、次のようなコマンドを実行した場合:

> py script.py -a "My Schedule"

プログラム内でargs.aを確認すると、"My Schedule"という文字列が取得できます。

プログラム全体のコード

以下に、sch.pyのコードを示します。 プログラムの説明はコードの後にあります。 このプログラムは、スケジュールの追加、一覧表示、削除の3つの機能を持っています。

import argparse
import json
import os

DATA_FILE = "schedule.json"

def load_data():
    """スケジュールデータをロードします"""
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE, "r") as f:
            return json.load(f)
    else:
        return []

def save_data(data):
    """スケジュールデータを保存します"""
    with open(DATA_FILE, "w") as f:
        json.dump(data, f, indent=4)

def add_schedule(date, time, title):
    """スケジュールを追加します"""
    d = load_data()
    s = {"id": len(d) + 1, "date": date, "time": time, "title": title}
    d.append(s)
    save_data(d)
    print(f"スケジュールを追加しました: {date} {time} - {title}")

def list_schedule(date):
    """指定された日付のスケジュールを一覧表示します"""
    d = load_data()
    new_id = d[-1]["id"] + 1 if d else 1
    s = {"id": new_id, "date": date, "time": time, "title": title}
    if l:
        print(f"スケジュール一覧 ({date}):")
        for s in l:
            print(f"{s['id']}. {s['time']} - {s['title']}")
    else:
        print(f"{date}のスケジュールはありません。")

def delete_schedule(schedule_id):
    """指定されたIDのスケジュールを削除します"""
    d = load_data()
    d = [s for s in d if s["id"] != schedule_id]
    save_data(d)
    print(f"スケジュール(ID: {schedule_id})を削除しました。")

def main():
    """メイン関数: コマンドライン引数を解析し、処理を分岐します"""
    parser = argparse.ArgumentParser(description="スケジュール管理プログラム")
    parser.add_argument("-a", nargs=3, metavar=("DATE", "TIME", "TITLE"), help="スケジュールを追加します")
    parser.add_argument("-l", metavar="DATE", help="スケジュールを一覧表示します")
    parser.add_argument("-d", type=int, metavar="ID", help="スケジュールを削除します")
    
    args = parser.parse_args()
    
    if args.a:
        add_schedule(args.a[0], args.a[1], args.a[2])
    elif args.l:
        list_schedule(args.l)
    elif args.d:
        delete_schedule(args.d)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

プログラムの説明

定数

定数とは常に同じ値を持ち、値を変えない変数のことをいいます。 Pythonは、言語的に定数を持っていないので、変数を定数としても使います。

  • すべて大文字で記述: DATA_FILEのように、大文字の名前が慣習的に定数を意味する。
  • 変更しない前提で利用: 定数として扱う変数は、コード中で変更されないように設計する。

すなわち、プログラマーがこのようなルールを守ることで、定数を使えるようになります。 DATA_FILEは実際は変数なので値を書き換えられますが、それをすると混乱の元になるので、しないようにしてください。

関数の説明コメント

関数の説明がdef文の次の行にあります。 この説明に使われているのは、3個のダブルクォート(トリプルクォート)で囲んだ文字列です。 ダブルクォートの代わりにシングルクォートを使うこともできます。 トリプルクォートの文字列は複数行に渡って記述することができます。

プログラムの説明はコメント文(ハッシュマーク#以後がコメントになる)を使うのが普通ですが、ここで文字列を使っている理由を説明します。

この文字列はDocstringと呼ばれます。 Docstringは、Pythonのコードに関する説明やドキュメントを記述するための文字列です。 クラス、関数、メソッド、モジュールなどの冒頭に記述され、コードの動作や使い方を明確に伝えるために利用されます。 Docstringにはトリプルクォートを使います。

  • この文字列は、関数などの__doc__属性に保存される
  • Docstringはhelp関数で参照することができる
  • ドキュメント生成ツール(例: Sphinx)で利用される

ハッシュマークのコメント文を使うと、そのコメントは実行時に無視されてしまうので、関数などの説明には必ず文字列を使ってください。 ドキュメント生成ツールについては、今後別の記事で説明する予定です。

load_data()save_data()

  • 役割: スケジュールデータを読み込みまたは保存します。ファイル形式はJSONです。
  • ポイント:

add_schedule(date, time, title)

  • 役割: 新しいスケジュールをデータに追加します。
  • ポイント:
    • ローカル変数dを使ってスケジュールリストを管理。
    • sは新規スケジュールを表す辞書。
    • 新しいidの作成部分は次の「条件式」の項を参照。

条件式

Pythonには式の中に条件を含む「条件式」があります。 C言語三項演算子(A ? B : C)と同等のものですが、構文が違います。

<Trueのときの値> if <条件> else <Falseのときの値>

この構文は次のように動作します。

  • <条件>を評価。
  • 条件がTrueの場合、<Trueのときの値>が選ばれ、その値が返される。
  • 条件がFalseの場合、<Falseのときの値>が選ばれ、その値が返される。

if文との違い

  • 通常のif文は、条件に応じてスイート内のコードを実行する。文であって式ではないので、値を返すことはしない。
  • 条件式は値を返す。同様のことはif文を使ってもできるが、複数行にわたり長くなる。条件式を使えば1行でシンプルに記述できる。

プログラム中の条件式の動作

new_id = d[-1]["id"] + 1 if d else 1

スケジュールのリストdを評価する。 このとき、リストが空ならFalse、そうでなければTrueと判定します(このあと詳しく説明)。 したがって

  • リストが空でなければ、リストの最後のidに1を加えたものをnew_idに代入する。
  • リストが空ならば、1をnew_idに代入する。

Pythonのif文などの式評価では、bool型オブジェクト(TrueとFalse)以外も使えます。 以下の値がFalse(偽)として評価されます(「Falsy」とも呼ばれる)。

  • None
  • 数値の0(0, 0.0, 0jなど)
  • 空のシーケンス([], "", ())
  • 空のマッピング({})
  • False

それ以外の値はすべてTrue(真)として評価されます(「Truthy」とも呼ばれる)。

list_schedule(date)

  • 役割: 指定された日付のスケジュールを表示します。
  • ポイント:
    • リスト内包表記(後述)を使用して、条件に一致するスケジュールを抽出。
    • 結果が空の場合、適切なメッセージを表示します。

リスト内法表記

Pythonリスト内包表記は、簡潔で読みやすい方法でリストを作成するための構文です。 1行でリストの生成ができます。

リスト内包表記の基本構文

以下が基本的な構文です:

[<式> for <変数> in <イテラブル> if <条件>]  # 条件部分は省略可能
  • <式>: リストに追加される要素を表します。
  • <変数>: forループで使われる変数です。
  • <イテラブル>: リストや文字列、範囲などの反復可能オブジェクトです。
  • if <条件>: 任意の条件を指定できます。この条件を満たす場合のみリストに要素が追加されます。

sch.pyでは、リスト内包表記を使ってスケジュールを抽出しています。

l = [s for s in d if s["date"] == date]
  • s for s in d:
    • リストdの各要素(s)を走査します。
  • if s["date"] == date:
    • スケジュールの"date"が指定されたdateと一致する場合のみ、sをリストに追加します。
  • 結果:
    • 条件に一致するスケジュールのリストが作成されます。

同じ処理を通常のforループで書くこともできますが、1行では書けません。

l = []
for s in d:
    if s["date"] == date:
        l.append(s)

リスト内包表記を使うことで、これを1行で簡潔に記述できます。

リスト内包表記の利点

  1. 簡潔で読みやすい:
    • 1行でリストを生成でき、コードが短くなります。
  2. 高速:
    • 内部で最適化されているため、通常のforループより高速に動作する場合があります。
  3. 柔軟性:
    • 条件式や複雑な処理を組み込むことができます。

リスト内包表記は糖衣構文として実装されています。 ここで、糖衣構文について簡単に説明します。

糖衣構文とは、プログラムの処理内容を簡潔に書けるようにするための表記方法です。 糖衣構文は新しい文法を追加したわけではなく、実際にはそれを同等な通常の構文に変換してPythonが解釈する仕組みです。 例えば、sch.pyの糖衣構文は、forループに書き直してPythonに解釈、実行されます。

リスト内包表記

l = [s for s in d if s["date"] == date]

展開されたforループ

l = []
for s in d:
    if s["date"] == date:
        l.append(s)

糖衣構文の特徴

    1. プログラムの本質には影響を与えない:
    2. 糖衣構文は、単にコードを簡潔に記述できる手段であり、実行される処理内容は通常の構文と同じです。
    1. 新しい機能ではなく既存の仕組みの簡略化:
    2. 糖衣構文は新しいPythonの文法ではなく、既存の機能を別の形で書きやすくしたものです。
    1. 例: リスト内包表記:
    2. Pythonがリスト内包表記をサポートするのは、forループとif文を簡潔に記述できるようにするためです。

なお、糖衣構文はPythonに組み込まれているもので、プログラマーが糖衣構文を作ることはできません。

delete_schedule(schedule_id)

  • 役割: 指定されたIDのスケジュールを削除します。
  • ポイント:
    • 削除後のデータリストを更新する際にもリスト内包表記を活用。

main()

  • 役割: コマンドライン引数を解析し、適切な関数を呼び出します。
  • ポイント:
    • -a, -l, -dといったオプションを指定し、それに応じた処理を実行。argparseモジュールの補足説明は次の項で行います。
    • 引数が不足している場合はヘルプメッセージを表示します。

argparseモジュールについての補足説明

1. -a 引数
parser.add_argument("-a", nargs=3, metavar=("DATE", "TIME", "TITLE"), help="スケジュールを追加します")

オプションの意味

  • -a:

    • ショートオプション。コマンドライン-aを指定することで、この引数を利用できます。
  • nargs=3:

    • この引数は3つの値(引数)を必要とします。
    • たとえば、python sch.py -a 2023-12-10 14:00 "Meeting"とすると、2023-12-1014:00Meetingがそれぞれ別々に渡されます。
  • metavar=("DATE", "TIME", "TITLE"):

    • ヘルプメッセージに表示されるプレースホルダー名です。
    • コマンドライン引数として何を入力するべきかを示します。
    • 例えば、python sch.py --helpで次のように表示されます:
-a DATE TIME TITLE  スケジュールを追加します
  • help="スケジュールを追加します":
    • ヘルプメッセージに表示されるこの引数の説明です。
2. -l 引数
parser.add_argument("-l", metavar="DATE", help="スケジュールを一覧表示します")

オプションの意味

  • -l:

    • ショートオプション。コマンドライン-lを指定することで、この引数を利用できます。
  • metavar="DATE":

    • ヘルプメッセージで、この引数に入力するべき値をDATEとして表示します。
    • 例えば、python sch.py --helpで次のように表示されます:
-l DATE  スケジュールを一覧表示します
  • help="スケジュールを一覧表示します":
    • ヘルプメッセージに表示されるこの引数の説明です。
3. -d 引数
parser.add_argument("-d", type=int, metavar="ID", help="スケジュールを削除します")

オプションの意味

  • -d:

    • ショートオプション。コマンドライン-dを指定することで、この引数を利用できます。
  • type=int:

    • この引数に指定される値を整数型に変換します。
    • ユーザーが文字列や浮動小数点数などの無効な値を入力すると、自動的にエラーになります。
  • metavar="ID":

    • ヘルプメッセージで、この引数に入力するべき値をIDとして表示します。
    • 例えば、python sch.py --helpで次のように表示されます:
-d ID  スケジュールを削除します
  • help="スケジュールを削除します":
    • ヘルプメッセージに表示されるこの引数の説明です。
まとめ
  1. nargs:

    • 必要な引数の個数を指定します(例: nargs=3は3つの値が必要)。
  2. metavar:

    • ヘルプメッセージで引数の説明をわかりやすくするためのプレースホルダーを設定します。
  3. type:

    • 引数の型を指定し、不適切な型の入力を自動的にチェックします。
  4. help:

    • 引数の用途を説明する文字列で、--help実行時に表示されます。

__name____main__

if __name__ == "__main__":

この構文は、スクリプトが直接実行されたときだけ、その下のコードを実行するための仕組みです。

  • __name__:
    • Pythonが各モジュールに自動的に割り当てる特殊な変数。
    • スクリプトが直接実行された場合、__name__"__main__"となります。
    • 他のモジュールからインポートされた場合、__name__はモジュール名になります。

単にコマンドラインから実行するだけなら、この部分は必要ありません。 直接main関数を呼び出すだけで十分です。 しかし、次のような場合にはこのif文が必要になります。

  • そのプログラムが2つの可能性(1ー直接実行される、2ーモジュールとして他のプログラムに使われる)があるとき
  • そのプログラムのテストで、テスト・フレームワークがこのプログラムをインポートする可能性があるとき
  • ドキュメント生成ツール(例: Sphinx)を使うとき。Sphinxはそのプログラムをインポートするため

今回は、次回以降でテストを行うので、このif文をつけておきました。

実行例

  1. スケジュールの追加
> py sch.py -a 2023-12-10 14:00 "ミーティング"
スケジュールを追加しました: 2023-12-10 14:00 - ミーティング
  1. スケジュールの一覧表示
> py sch.py -l 2023-12-10
スケジュール一覧 (2023-12-10):
1. 14:00 - ミーティング
  1. スケジュールの削除
> py sch.py -d 1
スケジュール(ID: 1)を削除しました。

まとめ

本記事では、Pythonを使ったスケジュール管理プログラムを作成しました。

  1. argparseモジュールを使ったコマンドライン引数の処理
  2. JSONファイルを使ったデータの永続化
  3. 関数を分割して明確に役割を定義する設計
  4. 定数、Docstring、リスト内包表記

このプログラムは、コマンドラインツールの作成方法の例となります。 しかし、これだけで十分なわけではなく、テスト、パッケージ化なども必要です。 それらは、次回以降の記事で取り上げたいと思います。

Python初級者のお勉強ノート(19)csvモジュール

PythonCSVファイルを扱う方法:csvモジュールの基本と応用

CSVファイルは、表形式のデータをテキスト形式で保存するための一般的なフォーマットです。 この記事では、Pythoncsvモジュールを使用してCSVファイルを扱う方法について説明します。 CSVデータを効率よく操作するためのプログラム例も紹介します。

CSVとは何か?

CSV(Comma-Separated Values)は、表形式のデータをカンマ(,)区切りで記述したテキスト形式のデータフォーマットです。 行ごとにデータを区切り、各行は複数の値を含みます。たとえば以下のような形式が一般的です。

都道府県,人口,面積,企業数,学校数
東京都,13929286,2194,104934,1224
大阪府,8839469,1904,71283,903

CSVの特徴: - テキスト形式で軽量。 - 表計算ソフトやデータベースと容易にやり取り可能。 - 読み書きが簡単だが、構造が複雑なデータには向かない。

表形式のデータのPythonでの実装方法

Pythonで表形式のデータを扱うには主に以下の2つの方法があります。 その具体例は、後述のプログラム例を参考にしてください。

  1. 二重リスト(リストのリスト)

    • 各行をリストとして、全体をリストに格納。先頭行がタイトルの場合が多いが、タイトルなしで本体のみのCSVでも可能。
    • メリット: シンプルで、データの順序がそのまま反映される。
    • デメリット: 各値にアクセスする際、インデックスを使用するため、列名がないと可読性が低下。
  2. 辞書(リストの辞書)

    • 各行を辞書で表現し、全体をリストに格納。CSVの先頭行がタイトル行であることが必要。
    • メリット: 各値に列名でアクセス可能。
    • デメリット: 辞書化する手間がかかる。

Pythoncsvモジュールの使い方

csvモジュールは、PythonCSVファイルを扱うための標準モジュールです。 以下のプログラム例では、都道府県ごとのデータをCSVファイルに保存し、それを二重リスト形式および辞書形式で読み込みます。

プログラム例1: 二重リスト形式での保存と読み込み

import csv

# データ
data = [
    ["都道府県", "人口", "面積", "企業数", "学校数"],
    ["東京都", 13929286, 2194, 104934, 1224],
    ["大阪府", 8839469, 1904, 71283, 903],
]

# CSVファイルへの保存
with open("data.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(data)

# CSVファイルの読み込み
with open("data.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    result = []
    for row in reader:
        result.append(row)

print(result)

説明

  • ファイルをオープンするときにnewline=""を必ず指定する。これは、csvモジュール内で改行を出力するので、余計な改行が入らないようにするためである。
  • csv.writer: CSVファイルへの書き込みを行うwriterオブジェクトを返す。
  • writerows: 二重リストを一括でcsvファイルに書き込む。writerオブジェクトのメソッド。
  • csv.reader: CSVファイルの内容をリスト形式で読み込むreaderオブジェクト。
  • readerオブジェクトはfor文のなかで、csvから読み込んだ行を繰り返し変数に代入する。このように、繰り返し処理のできるオブジェクトは「イテラブル」と呼ばれる。
  • result.append(row): appendはリストのメソッドで、そのリストの最後に引数のデータを付け加える。

出力例:

[['都道府県', '人口', '面積', '企業数', '学校数'], ['東京都', '13929286', '2194', '104934', '1224'], ['大阪府', '8839469', '1904', '71283', '903']]

プログラム例2: 辞書形式での保存と読み込み

import csv

# データ
data = [
    {"都道府県": "東京都", "人口": 13929286, "面積": 2194, "企業数": 104934, "学校数": 1224},
    {"都道府県": "大阪府", "人口": 8839469, "面積": 1904, "企業数": 71283, "学校数": 903},
]

# CSVファイルへの保存
with open("data_dict.csv", "w", newline="", encoding="utf-8") as f:
    fieldnames = ["都道府県", "人口", "面積", "企業数", "学校数"]
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()  # ヘッダー行を書き込む
    writer.writerows(data)

# CSVファイルの読み込み
with open("data_dict.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    result = []
    for row in reader:
        result.append(row)

print(result)

説明

  • csv.DictWriter: 辞書形式のデータをCSVファイルに書き込むオブジェクトを生成する。csv.readerとの違いは後述
  • fieldnames: 列名(の順番)を指定するリスト。
  • writeheader: ヘッダー行をファイルに書き込む。
  • csv.DictReader: 辞書形式でCSVファイルを読み込むオブジェクトを生成。

出力例:

[{'都道府県': '東京都', '人口': '13929286', '面積': '2194', '企業数': '104934', '学校数': '1224'}, {'都道府県': '大阪府', '人口': '8839469', '面積': '1904', '企業数': '71283', '学校数': '903'}]

csv.readerとcsv.DictReaderの違い

この2つは「小文字だけ」と「大文字+小文字」の違いがありますが、その背景には次のような違いがあります。

  • csv.readerは関数である。そして、 readerオブジェクトを返す。
  • csv.DictReaderはクラスである。クラスなので大文字からはじまる(そしてキャメルケースである)。
  • csv.DictReader()はDictReaderのインスタンスを生成する。インスタンスの生成方法は以前のブログを参照。

両者とも、オブジェクト(インスタンス)を返すのは同じなのだが、その過程が、関数呼び出しなのか、インスタンス生成なのかが違います。 細かいことになりますが、大文字が使われたり、使われなかったりの理由は、そういうことなのです。

まとめ

本記事では、PythonCSVファイルを扱う方法について学びました。

  1. CSVとは何か

    • 表形式データを扱う軽量フォーマット。
  2. 表形式のデータのPythonでの実装方法

    • 二重リスト形式: シンプルだが、列名でのアクセスができない。
    • 辞書形式: 可読性が高いが、若干のオーバーヘッドあり。
  3. csvモジュールの使い方

    • csv.writercsv.DictWriterCSVファイルにデータを保存。
    • csv.readercsv.DictReaderでデータを読み込む。

Python初級者のお勉強ノート(18)requestsモジュール

前回の記事ではJSONについて学びました。 今回はその応用として、APIを利用してデータを取得する方法を学びます。 具体的には、Pythonrequestsモジュールを使って、天気予報APIであるOpen-Meteoから気象データを取得するプログラムを作成します。

requestsモジュールの概要

requestsモジュールは、PythonでHTTPリクエストを簡単に扱うためのライブラリです。 直感的で使いやすいインターフェースを持ち、GETリクエストやPOSTリクエストをはじめとしたさまざまな操作をサポートします。

requestsモジュールのインストール方法

requestsモジュールはPythonの標準ライブラリではないため、事前にインストールが必要です。 以下のコマンドを実行してください。

pip install requests

HTTPの概要

HTTP(Hypertext Transfer Protocol)は、ウェブ上でデータをやり取りするためのプロトコルプロトコルとはやり取りのルール)です。 このやり取りをするのは、クライアントとサーバです。 例えばあなたが、ウェブサイトを見るときは、あなたのPCがクライアント、ウェブサイトのPCがサーバです。 HTTPでは、まずクライアントがサーバにリクエスト(要求)を出し、サーバがそれに応答します。

主に以下のリクエストメソッドが使用されます。

  • GET: サーバーからデータを取得する。
  • POST: サーバーにデータを送信する。
  • PUT: サーバー上のデータを更新する。
  • DELETE: サーバー上のデータを削除する。

ここでは、最も基本的なGETリクエストを使用してデータを取得します。

GETリクエストの問い合わせと応答プロセス

  1. クライアントがサーバのURLにGETリクエストを送信します。
  2. サーバーがリクエストを処理し、結果(レスポンス)を返します。
  3. クライアントはレスポンスからデータを取得して処理します。

httpbin.orgrequestsモジュールを使ったテスト

httpbin.orgは、HTTPリクエストやレスポンスの動作を確認するためのテストサービスです。 HTTPのサーバ側になります。 これを使ってrequestsモジュールの使い方を学びましょう。

import requests

url = "https://httpbin.org/get"
response = requests.get(url)

data = response.json()
print(f"取得データ: {data}")
  • requestsモジュールをインポートする。
  • getメソッドを使って、サーバにアクセスし、そのレスポンス(Responseオブジェクト)を変数responseに代入する。
  • このオブジェクトのjsonメソッドは、レスポンスがjsonに基づいている場合に使う。jsonメソッドはjsonを辞書型やリストに変更し、それを返す。

requestsモジュールで良く使うメソッドは、この記事の最後にまとめておきますので、参照してください。

Open-Meteoの概要

Open-Meteoは、無料で利用可能な気象データAPIです。以下のようなデータを取得できます。

  • 気温: 地上2メートルの気温。
  • 降水量: 1時間あたりの降水量。
  • 風速・風向: 地上10メートルの風速と風向。
  • 日射量: 太陽放射エネルギー量。

詳しいことは、Open-Meteoのウェブサイトを参照して下さい。

Open-Meteoが返すJSONデータの説明

Open-MeteoのAPIから取得したデータは、以下のようなJSON形式で返されます。

{
    'latitude': 35.676624,
    'longitude': 139.69112,
    'generationtime_ms': 0.07390975952148438,
    'utc_offset_seconds': 32400,
    'timezone': 'Asia/Tokyo',
    'timezone_abbreviation': 'JST',
    'elevation': 40.0,
    'daily_units':
        {
            'time': 'iso8601',
            'temperature_2m_max': '°C',
            'temperature_2m_min': '°C',
            'precipitation_sum': 'mm',
            'windspeed_10m_max': 'km/h',
            'wind_direction_10m_dominant': '°',
            'shortwave_radiation_sum': 'MJ/m²'
        },
    'daily':
        {
            'time': ['2024-11-01'],
            'temperature_2m_max': [21.2],
            'temperature_2m_min': [10.5],
            'precipitation_sum': [5.9],
            'windspeed_10m_max': [13.2],
            'wind_direction_10m_dominant': [345],
            'shortwave_radiation_sum': [11.03]
        }
}

実用プログラム例: Open-Meteoで東京の気象データを取得

以下のプログラムは、東京(緯度: 35.6895、経度: 139.6917)の2024年11月1日の気象データを取得し、最高気温や降水量などを箇条書きで出力します。

import requests

url = "https://archive-api.open-meteo.com/v1/archive"

params = {
    "latitude": 35.6895,
    "longitude": 139.6917,
    "daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max,wind_direction_10m_dominant,shortwave_radiation_sum",
    "timezone": "Asia/Tokyo",
    "start_date": "2024-11-01",
    "end_date": "2024-11-01"
}

response = requests.get(url, params=params)
data = response.json()

# データの整理
daily = data["daily"]
temperature_max = daily["temperature_2m_max"][0]
temperature_min = daily["temperature_2m_min"][0]
precipitation_sum = daily["precipitation_sum"][0]
windspeed = daily["windspeed_10m_max"][0]
winddirection = daily["wind_direction_10m_dominant"][0]
radiation = daily["shortwave_radiation_sum"][0]

print("2024年11月1日の東京の気象データ:")
print(f"  最高気温: {temperature_max}℃")
print(f"  最低気温: {temperature_min}℃")
print(f"  総降水量: {precipitation_sum}mm")
print(f"  最大風速: {windspeed}km/h, 風向: {winddirection}°")
print(f"  日射量: {radiation}MJ/m²")

プログラムの実行結果

PS > py w.py
2024年11月1日の東京の気象データ:
  最高気温: 21.2℃
  最低気温: 10.5℃
  総降水量: 5.9mm
  最大風速: 13.2km/h, 風向: 345°
  日射量: 11.03MJ/m²
PS >

requestsモジュールのよく使うメソッド

Pythonrequestsモジュールには、HTTPリクエストを送信するための便利なメソッドが用意されています。以下は、よく使うメソッドの解説です。

1. requests.get(url, params=None, **kwargs)

  • 用途: サーバーからデータを取得する(HTTP GETリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
    • params: URLクエリパラメータを辞書型で指定。
  • :
response = requests.get("https://httpbin.org/get", params={"key": "value"})
print(response.url)  # https://httpbin.org/get?key=value

2. requests.post(url, data=None, json=None, **kwargs)

  • 用途: サーバーにデータを送信する(HTTP POSTリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
    • data: フォームデータ(application/x-www-form-urlencoded形式)。
    • json: JSONデータ(application/json形式)。
  • :
response = requests.post("https://httpbin.org/post", json={"key": "value"})
print(response.json())  # サーバーが受け取ったデータを確認

3. requests.put(url, data=None, **kwargs)

  • 用途: サーバー上のリソースを更新または置換する(HTTP PUTリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
    • data: 更新するデータ。
  • :
response = requests.put("https://httpbin.org/put", data={"key": "updated_value"})
print(response.json())

4. requests.delete(url, **kwargs)

  • 用途: サーバー上のリソースを削除する(HTTP DELETEリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
  • :
response = requests.delete("https://httpbin.org/delete")
print(response.status_code)  # 200(成功)

5. requests.head(url, **kwargs)

  • 用途: レスポンスヘッダーのみを取得する(HTTP HEADリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
  • :
response = requests.head("https://httpbin.org/get")
print(response.headers)

6. requests.patch(url, data=None, **kwargs)

  • 用途: サーバー上のリソースを部分的に更新する(HTTP PATCHリクエスト)。
  • 主な引数:
    • url: リクエスト先のURL。
    • data: 更新するデータ。
  • :
response = requests.patch("https://httpbin.org/patch", data={"key": "patched_value"})
print(response.json())

まとめ

この記事では以下の内容を学びました。

  1. requestsモジュールの概要と基本的な使い方。
  2. HTTP GETリクエストの仕組みとテスト方法。
  3. Open-Meteo APIの利用方法と実用的なプログラム例。

requestsモジュールを使えば、簡単にAPIを利用してデータを取得できます。 Open-MeteoのようなAPIを活用して、様々な情報を入手するプログラムを書くことが可能です。

Python初級者のお勉強ノート(17)jsonモジュール

Pythonを学ぶ上で、データのやり取りや保存方法の基本としてJSON形式を理解することは重要です。 この記事では、Python標準モジュールのjsonを取り上げ、その基本と実用例を解説します。

JSONとは?

JSONJavaScript Object Notation)は、データを簡潔かつ人間が読める形式で表現する軽量なデータフォーマットです。
JSONは、もともとJavaScriptで利用されていましたが、現在ではプログラミング言語を問わず、広く使われています。
特に、APIを介したデータ交換や設定ファイル、データの保存形式として重要な役割を果たしています。

具体的には、次のような特徴があります: - テキスト形式で軽量。 - 構造化されたデータ(リストや辞書のようなもの)を表現可能。 - 人間にも読みやすく、機械が解析しやすい。

JSONの構造と形式

JSONは、次のような基本構造を持ちます:

オブジェクト(Object)

  • { }で囲まれたキーと値のペアの集合。
  • キーは文字列、値は文字列・数値・オブジェクトなど。
{
  "name": "Alice",
  "age": 25,
  "isStudent": false
}

配列(Array)

  • [ ]で囲まれた値のリスト。
  • 値は文字列・数値・オブジェクトなどを含められる。
{
  "colors": ["red", "green", "blue"]
}

JSONで使えるデータ型

  • 文字列: "hello"
  • 数値: 42, 3.14
  • 真偽値: true, false
  • null: null
  • オブジェクト: {...}
  • 配列: [...]

JSONが使われる場面

コンピュータ内部では、Pythonのリスト、辞書、数値などのデータ構造を使い、高速に処理を行うことができます。 これらは文字列ではありませんので、コンピュータ外部にそのまま持ち出せません。 そこで、それらを文字列に変換して、サーバと通信したり、ディスプレイに表示したりします。 この文字列変換には、いくつかの方法が考えられますが、最も簡単なのがJSONです。

JSONは多くの場面で使われています。その例として以下が挙げられます:

  • Web APIとのデータ交換
    クライアントとサーバー間のデータ送受信に使用されます。例えば、天気予報APIや為替レートAPIは、データをJSON形式で提供します。

  • 設定ファイル
    アプリケーションの設定情報を保存する形式として利用されます。例えば、VSCodeの設定ファイル(settings.json)はJSON形式です。

  • データの永続化
    小規模なデータベース代わりに、JSON形式でデータを保存することもあります。

PythonJSONを扱う:jsonモジュール

Pythonの標準モジュールであるjsonを使うと、JSONデータをPythonのデータ型(辞書やリストなど)と相互に変換したり、JSONファイルを操作できます。

jsonモジュールの主な機能

jsonは単純な言い方をすれば、オブジェクトと配列からなるテキストです。 それは、自然にPythonの辞書とリストに対応させることができます。 その他の要素も次の表のように対応させることができます。

JSONの型 Pythonの型
オブジェクト 辞書 (dict)
配列 リスト (list)
文字列 (string) 文字列 (str)
数値 (number) 整数 (int) または浮動小数点数 (float)
真偽値 (true/false) True / False
null None

この表に従って次のような双方向の変換がjsonモジュールではサポートされています。

  1. JSON文字列とPythonデータ型の変換

    • JSON文字列からPythonの辞書やリストに変換。
    • Pythonの辞書やリストをJSON文字列に変換。
  2. JSONファイルの読み書き

    • JSON形式のファイルを読み込んでPythonのデータ型に変換。
    • Pythonのデータ型をJSON形式でファイルに保存。

JSONモジュールでよく使うメソッド

1. json.dumps(obj, **kwargs)

PythonオブジェクトをJSON文字列に変換します。

例:
import json

data = {"name": "Alice", "age": 25}
s = json.dumps(data)
print(s)  # {"name": "Alice", "age": 25}
主な引数:
  • indent: インデントを指定し、読みやすく整形。
  • sort_keys: キーをアルファベット順にソート。
整形した出力:
s = json.dumps(data, indent=4, sort_keys=True)
print(s)

次のように整形されて表示されます。

{
    "age": 25,
    "name": "Alice"
}

2. json.loads(s)

JSON文字列をPythonの辞書やリストに変換します。

例:
s = '{"name": "Alice", "age": 25}'
data = json.loads(s)
print(data["name"])  # Alice

3. json.dump(obj, fp, **kwargs)

PythonオブジェクトをJSON形式でファイルに保存します。

例:
data = {"name": "Alice", "age": 25}

with open('data.json', 'w') as f:
    json.dump(data, f, indent=4)

4. json.load(fp)

JSON形式のファイルをPythonのデータ型に変換して読み込みます。

例:
with open('data.json', 'r') as f:
    data = json.load(f)
print(data)

実用例:JSON形式の設定ファイルを読み書きする

Pythonプログラムで設定を管理する際、JSON形式のファイルを利用すると便利です。

サンプル設定ファイル(config.json

{
  "theme": "dark",
  "fontSize": 14,
  "autoSave": true
}

設定を読み込むプログラム

import json

# 設定ファイルを読み込む
with open('config.json', 'r') as f:
    config = json.load(f)

# 設定を表示
print(f"テーマ: {config['theme']}")
print(f"フォントサイズ: {config['fontSize']}")
print(f"自動保存: {'有効' if config['autoSave'] else '無効'}")

設定を変更して保存するプログラム

# 設定を変更
config['theme'] = 'light'

# 設定ファイルを更新
with open('config.json', 'w') as f:
    json.dump(config, f, indent=4)

まとめ

  1. JSONとは

    • 軽量で人間が読みやすいデータ形式。主にデータ交換や保存に使用される。
  2. Pythonjsonモジュール

    • JSON文字列の変換やファイル操作を簡単に行うための標準モジュール。
  3. よく使うメソッド

    • json.dumps, json.loads: JSON文字列との変換。
    • json.dump, json.load: JSONファイルとの変換。
  4. 実用例

    • 設定ファイルの読み書きやデータの永続化。

Python初級者のお勉強ノート(16)sysモジュール

Pythonsysモジュールは、Pythonインタープリタやその環境に関する情報を提供し、プログラムの挙動を制御する機能を備えています。 このモジュールを使うことで、Pythonスクリプトをより柔軟に制御できます。 本記事では、sysモジュールの基本的な機能を解説します。

sysモジュールとは?

sysモジュールは、Python標準ライブラリの一部で、次のようなタスクをサポートします。

利用するには、以下のようにインポートします。

import sys

sysモジュールの主な機能と使い方

コマンドライン引数の取得

スクリプトコマンドラインから渡された引数を取得する場合に使用します。 これについては、すでに「Python初心者のお勉強ノート(5)コマンドライン引数の参照と例外処理」でも解説していますので、参考にしてください。

sys.argv

リストとしてコマンドライン引数を取得します。リストの0番目はスクリプト名が格納され、それ以降に引数が続きます。

import sys

print("スクリプト名:", sys.argv[0])
if len(sys.argv) > 1:
    print("渡された引数:", sys.argv[1:])
else:
    print("引数はありません")

実行例:

上のプログラムをscript.pyという名前で保存しておきます。

> python script.py arg1 arg2
スクリプト名: script.py
渡された引数: ['arg1', 'arg2']

Pythonインタープリタの情報

sys.version

現在使用しているPythonのバージョン情報を取得します。

sys.platform

現在のOSを示す文字列を取得します。

>>> import sys
>>> sys.version
'3.13.0 (tags/v3.13.0:60403a5, Oct  7 2024, 09:38:07) [MSC v.1941 64 bit (AMD64)]'
>>> sys.platform
'win32'
>>>

標準入出力の操作

sys.stdoutsys.stderr

通常の出力(標準出力)やエラー出力(標準エラー出力)を制御します。これらをカスタマイズすることで、出力先を変更できます。

例: 標準エラー出力にメッセージを出力

import sys

sys.stderr.write("エラーメッセージ\n")
sys.stdin

標準入力を操作できます。 柔軟な入力処理を行う際に役立ちます。

標準入出力については「Python初心者のお勉強ノート(6)標準入力と標準出力、ファイルの読み書き」でも解説していますので、参考にしてください。

プログラムの終了

sys.exit()

プログラムを終了する際に使用します。引数に終了ステータスコードを指定できます。

import sys

print("プログラムを終了します")
sys.exit(0)

0は正常終了を、0以外はエラー終了を意味します。 この値は、プログラムを呼び出した親プロセスに返されます。

パス操作とモジュール検索

sys.path

Pythonがモジュールを検索するパスのリストです。このリストを操作することで、カスタムのモジュール検索パスを追加できます。

import sys

print("モジュール検索パス:")
for p in sys.path:
    print(p)

特定のディレクトリをモジュール検索パスに追加する例:

import sys

sys.path.append('/path/to/custom_modules')

メモリ管理情報

sys.getsizeof()

オブジェクトのメモリ使用量(バイト単位)を取得します。

>>> sys.getsizeof([1,2,3])
88
>>>

まとめ

sysモジュールを使うと、Pythonインタープリタやシステム環境に関する情報を取得し、プログラムの挙動を柔軟に制御できます。 本記事では以下の内容を学びました。

  1. コマンドライン引数の取得
  2. Pythonインタープリタ情報の確認
  3. 標準入出力の操作
  4. プログラムの終了やモジュール検索パスの管理

これらを活用すれば、環境に応じた柔軟なプログラムを作成できます。