Python応用構文

この講座では、Pythonの応用的な構文について学びます。

  • Noneの扱い
  • 例外処理(try / except / finally)
  • コマンドライン引数(sys.argv)
  • リスト内包表記
  • ラムダ関数(lambda)
  • enumerate と zip

1. 事前準備

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

2. Noneの扱い

2.1 Noneとは

Noneは、Pythonにおける「値がない」ことを表す特別な値です。他のプログラミング言語における nullnil に相当します。変数を初期化する際や、関数が明示的に値を返さない場合に使用されます。

Noneで変数を初期化する基本的な構文は以下のとおりです。

変数名 = None

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、Noneの基本的な動作を確認するものです。

# Noneで変数を初期化
result = None
print(result)
print(type(result))

実行すると以下のように出力されます。

None
<class 'NoneType'>

result = None で変数にNoneを代入し、print(result)None と表示されています。type(result)<class 'NoneType'> を返しており、NoneはPython独自の NoneType 型であることがわかります。

2.2 Noneの判定

Noneかどうかを判定するには、== ではなく is 演算子を使います。基本的な構文は以下のとおりです。

if 変数名 is None:
    # 値がNoneの場合の処理

if 変数名 is not None:
    # 値がNoneでない場合の処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、Noneの判定方法を確認するものです。

value = None

# is を使った判定(推奨)
if value is None:
    print("値はNoneです")

if value is not None:
    print("値が設定されています")
else:
    print("値が設定されていません")

実行すると以下のように出力されます。

値はNoneです
値が設定されていません

valueNone なので、is None の条件が True となり「値はNoneです」が表示されます。続いて is not None の条件は False となるため else ブロックが実行され、「値が設定されていません」が表示されています。

💡 ポイント
Noneの判定には == None ではなく is None を使いましょう。is はオブジェクトの同一性を比較する演算子で、Noneの判定ではこちらが正しい書き方です。Pythonの公式スタイルガイド(PEP 8)でも is None / is not None の使用が推奨されています。

2.3 関数とNone

関数が return を明示的に記述しない場合、自動的に None が返されます。

def 関数名(引数):
    # returnを記述しない処理

結果 = 関数名(引数)
# 結果 は None になる

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、戻り値のない関数がNoneを返すことを確認するものです。

def greet(name):
    print(f"こんにちは、{name}さん!")

# 戻り値のない関数の返り値を確認
result = greet("田中")
print(f"戻り値: {result}")
print(f"戻り値はNone: {result is None}")

実行すると以下のように出力されます。

こんにちは、田中さん!
戻り値: None
戻り値はNone: True

greet() 関数は print() で出力するだけで return を記述していないため、戻り値は自動的に None になります。result に受け取った値を表示すると Noneresult is NoneTrue となり、戻り値がないことが確認できます。

2.4 Noneの実践的な使い方

Noneは、検索結果が見つからなかった場合や、オプションの値が未指定の場合などに活用します。以下は、検索関数でNoneを返すパターンの基本的な構文です。

def 検索関数(リスト, 検索値):
    for 要素 in リスト:
        if 要素 == 検索値:
            return 要素
    return None  # 見つからなかった場合

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、辞書からユーザを検索し、見つからなかった場合にNoneを返すものです。

def find_user(users, target_name):
    for user in users:
        if user["name"] == target_name:
            return user
    return None

users = [
    {"name": "田中太郎", "age": 25},
    {"name": "鈴木花子", "age": 30},
    {"name": "佐藤次郎", "age": 22},
]

# 存在するユーザを検索
result = find_user(users, "鈴木花子")
if result is not None:
    print(f"見つかりました: {result['name']}{result['age']}歳)")

# 存在しないユーザを検索
result = find_user(users, "山田三郎")
if result is None:
    print("ユーザが見つかりませんでした")

実行すると以下のように出力されます。

見つかりました: 鈴木花子(30歳)
ユーザが見つかりませんでした

find_user() は一致するユーザが見つかれば辞書を返し、見つからなければ None を返します。"鈴木花子" は存在するため辞書が返り、is not None の条件で情報が表示されます。"山田三郎" は存在しないため None が返り、is None の条件で「見つかりませんでした」が表示されています。

3. 例外処理

3.1 try / except

例外処理は、プログラム実行中に発生するエラーを適切に処理するための仕組みです。ファイルの読み込み失敗やデータの不正など、実行時に起きうるエラーへの対処が重要です。

try / except / finallyの処理の流れを以下の図に示します。

flowchart TD
    A(["開始"]) --> B["tryブロックの処理"]
    B -- "例外なし" --> C["正常な処理を継続"]
    B -- "例外が発生" --> D{"例外の型を判定"}
    D -- "一致する<br>exceptあり" --> E["該当するexceptブロック"]
    D -- "一致する<br>exceptなし" --> F["例外が再送出される"]
    C --> G["finallyブロック<br>(必ず実行)"]
    E --> G
    F --> G
    G --> H(["終了"])

基本的な構文は以下のとおりです。

try:
    # エラーが発生する可能性のある処理
except 例外型:
    # エラー処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう(config.json は作成しないでください)。このプログラムは、存在しないファイルを読み込もうとしたときのエラーを処理するものです。

try:
    with open("config.json", "r") as f:
        content = f.read()
        print("設定ファイルを読み込みました")
except FileNotFoundError:
    print("エラー: 設定ファイルが見つかりません")

config.json が存在しないため、実行すると以下のように出力されます。

エラー: 設定ファイルが見つかりません

config.json が存在しないため open()FileNotFoundError が発生し、except ブロックのエラーメッセージが表示されています。try ブロック内の print("設定ファイルを読み込みました") は実行されていません。

3.2 複数の例外を処理する

異なる種類のエラーに対して、個別の処理を記述できます。基本的な構文は以下のとおりです。

try:
    # 処理
except 例外型1 as e:
    # 例外型1のエラー処理
except 例外型2:
    # 例外型2のエラー処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、数値変換と範囲チェックで複数の例外を処理するものです。

def parse_number(value_str):
    try:
        value = int(value_str)
        if value < 0 or value > 100:
            raise ValueError("値は0〜100の範囲で指定してください")
        return value
    except ValueError as e:
        print(f"エラー: {e}")
        return None
    except TypeError:
        print("エラー: 値が指定されていません")
        return None

# 正常なケース
result = parse_number("85")
print(f"値: {result}")  # 値: 85

# 不正な値
result = parse_number("abc")
print(f"値: {result}")  # 値: None

# 範囲外の値
result = parse_number("150")
print(f"値: {result}")

実行すると以下のように出力されます。

値: 85
エラー: invalid literal for int() with base 10: 'abc'
値: None
エラー: 値は0〜100の範囲で指定してください
値: None

"85" は正常に変換され 85 が返ります。"abc" は整数に変換できないため ValueError が発生し、"150" は変換後に範囲チェックで引っかかるため ValueError が発生します。いずれのエラーケースでも None が返されています。

3.3 finally

finally ブロックは、例外の有無に関わらず必ず実行される処理を記述します。基本的な構文は以下のとおりです。

try:
    # 処理
except 例外型:
    # エラー処理
finally:
    # 必ず実行される処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう(data.txt は作成しないでください)。このプログラムは、ファイル読み込みの成功・失敗に関わらずfinallyブロックが実行されることを確認するものです。

def read_data_file(filepath):
    try:
        with open(filepath, "r") as f:
            lines = f.read().splitlines()
            print(f"{len(lines)} 行のデータを読み込みました")
            return lines
    except FileNotFoundError:
        print(f"エラー: {filepath} が見つかりません")
        return []
    except PermissionError:
        print(f"エラー: {filepath} の読み込み権限がありません")
        return []
    finally:
        print("データファイルの読み込み処理が完了しました")

data = read_data_file("data.txt")
print(f"データ: {data}")

data.txt が存在しないため、実行すると以下のように出力されます。

エラー: data.txt が見つかりません
データファイルの読み込み処理が完了しました
データ: []

data.txt が存在しないため FileNotFoundErrorexcept ブロックが実行され、エラーメッセージが表示されています。その後、finally ブロックの「読み込み処理が完了しました」が例外の発生に関わらず実行されています。戻り値は空リスト [] です。

💡 ポイント
finally はファイルのクローズ処理やログの記録など、成功・失敗に関わらず必ず実行したい処理に使用します。ただし、with 文を使用している場合はファイルのクローズは自動的に行われるため、finally でのクローズ処理は不要です。

3.4 主要な組み込み例外

よく使われる例外クラスの一覧です。

例外 説明 発生例
FileNotFoundError ファイルが見つからない open("存在しないファイル")
PermissionError 権限がない 読み取り権限のないファイルを開く
ValueError 不正な値 int("abc")
TypeError 型が不正 "abc" + 123
KeyError 辞書のキーが存在しない dict["存在しないキー"]
IndexError リストの範囲外 list[999]
ConnectionError ネットワーク接続エラー API通信の失敗

4. コマンドライン引数

4.1 コマンドライン引数とは

コマンドライン引数とは、プログラムを実行する際にコマンドに続けて渡すパラメータのことです。例えばpython main.py helloと実行した場合、helloがコマンドライン引数です。

コマンドライン引数を使うと、プログラムのコードを変更せずに、実行時に動作を変えることができます。例えば、処理対象のファイル名や設定値を引数で渡すといった使い方ができます。

4.2 sys.argv

Pythonでは、sysモジュールのsys.argvを使ってコマンドライン引数を取得できます。基本的な構文は以下のとおりです。

import sys

引数 = sys.argv[番号]

sys.argvはリストで、sys.argv[0]にはスクリプト名、sys.argv[1]以降に渡された引数が順番に格納されます。

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

import sys

print(f"引数の数: {len(sys.argv)}")
print(f"すべての引数: {sys.argv}")
print(f"スクリプト名(argv[0]): {sys.argv[0]}")
print(f"第1引数(argv[1]): {sys.argv[1]}")
print(f"第2引数(argv[2]): {sys.argv[2]}")

引数を付けて実行してみましょう。

python main.py hello world

以下のように出力されます。

引数の数: 3
すべての引数: ['main.py', 'hello', 'world']
スクリプト名(argv[0]): main.py
第1引数(argv[1]): hello
第2引数(argv[2]): world

sys.argv["main.py", "hello", "world"]というリストになっています。sys.argv[0]にはスクリプト名が入り、sys.argv[1]以降にコマンドラインで指定した引数が順番に格納されます。引数の数は3つ(スクリプト名を含む)です。

📝 sys.argvの値はすべて文字列型
sys.argvの値はすべて文字列型です。数値として使う場合はint()float()で型変換が必要です。例えばpython main.py 100と実行した場合、sys.argv[1]は文字列の"100"であり、整数の100ではありません。
💡 ポイント
コマンドライン引数は、ツールやスクリプトに柔軟性を持たせるための基本的な仕組みです。処理対象のファイル名、出力先のパス、設定値など、実行のたびに変わる値を引数で渡すことで、コードを変更せずに異なる条件で実行できるようになります。

今回のようにターミナルから直接実行する場合はもちろん、シェルスクリプトやバッチファイルからPythonスクリプトを呼び出す場合にも、コマンドライン引数で値を渡すことができます。たとえば、毎日決まった時間に実行するスクリプトで処理対象の日付を渡したり、複数のファイルを順番に処理するシェルスクリプトからファイル名を渡したりと、他のプログラムと組み合わせて使う場面で広く活用されます。

5. リスト内包表記

5.1 基本的なリスト内包表記

リスト内包表記は、forループを使ったリスト生成を簡潔に記述するための構文です。基本的な構文は以下のとおりです。

[式 for 変数 in イテラブル]

実際に試してみましょう。従来のforループでの記述とリスト内包表記を比較する例です。main.py に以下の内容を記述して実行してみましょう。このプログラムは、forループとリスト内包表記で同じ結果が得られることを確認するものです。

# 従来のforループ
names = ["tanaka", "suzuki", "sato", "yamada"]
upper_names = []
for name in names:
    upper_names.append(name.upper())
print(upper_names)

# リスト内包表記(同じ結果が得られる)
upper_names = [name.upper() for name in names]
print(upper_names)

実行すると以下のように出力されます。

['TANAKA', 'SUZUKI', 'SATO', 'YAMADA']
['TANAKA', 'SUZUKI', 'SATO', 'YAMADA']

従来のforループでは append() を使って1つずつ追加していますが、リスト内包表記では [name.upper() for name in names] の1行で同じ結果が得られています。どちらも各要素を大文字に変換したリストが出力されています。

5.2 条件付きリスト内包表記

if を追加することで、条件に合う要素だけを抽出できます。基本的な構文は以下のとおりです。

[式 for 変数 in イテラブル if 条件]

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、条件付きリスト内包表記で特定のファイルだけを抽出するものです。

files = ["report.txt", "data.csv", "image.png", "notes.txt", "chart.csv"]

# テキストファイルだけを抽出
txt_files = [f for f in files if f.endswith(".txt")]
print(f"テキストファイル: {txt_files}")

実行すると以下のように出力されます。

テキストファイル: ['report.txt', 'notes.txt']

if f.endswith(".txt") の条件により、.txt で終わるファイルだけが抽出されています。5つのファイルのうち report.txtnotes.txt の2つが条件に一致し、リストとして返されています。

5.3 実践的な使用例

テストの点数リストから、合格点(60点以上)だけを抽出する例です。main.py に以下の内容を記述して実行してみましょう。このプログラムは、点数リストから合格者を抽出して評価を付与するものです。

scores = [45, 78, 92, 33, 67, 88, 51, 73, 95, 42]

# 合格点の抽出
passed = [s for s in scores if s >= 60]
print(f"合格者の点数: {passed}")

# 各点数に対して評価を付与
grades = {s: "優" if s >= 90 else "良" if s >= 70 else "可" for s in passed}
print(f"評価: {grades}")

実行すると以下のように出力されます。

合格者の点数: [78, 92, 67, 88, 73, 95]
評価: {78: '良', 92: '優', 67: '可', 88: '良', 73: '良', 95: '優'}

if s >= 60 の条件で60点以上の点数だけが抽出されています。さらに辞書内包表記で各点数に対して90点以上を「優」、70点以上を「良」、それ以外を「可」と評価を付与した辞書が生成されています。

📝 リスト内包表記の使いすぎに注意
リスト内包表記は簡潔に書ける反面、複雑な条件や処理を詰め込むと可読性が下がります。1行で読みづらくなる場合は、通常のforループで記述した方が保守しやすいコードになります。

6. ラムダ関数

6.1 ラムダ関数とは

ラムダ関数(lambda)は、名前を付けずに定義できる小さな関数です。def で定義する通常の関数と異なり、1行で簡潔に記述できます。基本的な構文は以下のとおりです。

lambda 引数: 式

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、通常の関数とラムダ関数の書き方を比較するものです。

# 通常の関数
def double(x):
    return x * 2

# ラムダ関数(同じ処理)
double_lambda = lambda x: x * 2

print(double(5))
print(double_lambda(5))

実行すると以下のように出力されます。

10
10

通常の def で定義した double() 関数と、lambda x: x * 2 で定義したラムダ関数の両方が 5 * 2 = 10 を返しており、同じ結果が得られています。

6.2 sorted() と組み合わせる

ラムダ関数は、sorted() 関数のソート基準を指定する場面でよく使われます。基本的な構文は以下のとおりです。

sorted(イテラブル, key=lambda 変数: 式)

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、辞書のリストを特定のキーでソートするものです。

students = [
    {"name": "田中", "score": 78},
    {"name": "鈴木", "score": 92},
    {"name": "佐藤", "score": 65},
    {"name": "山田", "score": 88},
]

# 点数の昇順でソート
by_score = sorted(students, key=lambda s: s["score"])
for s in by_score:
    print(f"{s['name']}: {s['score']}点")

print()

# 点数の降順でソート
by_score_desc = sorted(students, key=lambda s: s["score"], reverse=True)
for s in by_score_desc:
    print(f"{s['name']}: {s['score']}点")

実行すると以下のように出力されます。

佐藤: 65点
田中: 78点
山田: 88点
鈴木: 92点

鈴木: 92点
山田: 88点
田中: 78点
佐藤: 65点

key=lambda s: s["score"] により、各辞書の "score" の値をソート基準として使用しています。昇順では点数の低い佐藤(65点)から高い鈴木(92点)の順に、reverse=True を付けた降順ではその逆順に並んでいます。

💡 ポイント
ラムダ関数は「短い処理を1回だけ使う」場面に適しています。複雑な処理やくり返し使う関数には def で名前付き関数を定義しましょう。可読性を優先することが大切です。

7. enumerate と zip

7.1 enumerate

enumerateは、forループでリストを処理する際に、要素と一緒にインデックス(番号)を取得できる関数です。基本的な構文は以下のとおりです。

for インデックス, 要素 in enumerate(イテラブル):
    # インデックスと要素を使った処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、リストの要素を番号付きで表示するものです。

fruits = ["りんご", "バナナ", "オレンジ", "ぶどう"]

# インデックスなし(通常のfor)
for fruit in fruits:
    print(fruit)

print()

# インデックス付き(enumerate)
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

実行すると以下のように出力されます。

りんご
バナナ
オレンジ
ぶどう

0: りんご
1: バナナ
2: オレンジ
3: ぶどう

通常のforループでは要素のみが表示されますが、enumerate() を使うと 0 から始まるインデックスと要素をペアで取得できます。i にインデックス、fruit に要素が代入され、番号付きで表示されています。

enumerate() の第二引数に開始番号を指定することもできます。

for i, fruit in enumerate(fruits, 1):
    print(f"{i}. {fruit}")

実行すると以下のように出力されます。

1. りんご
2. バナナ
3. オレンジ
4. ぶどう

enumerate(fruits, 1) と第二引数に 1 を指定したことで、インデックスがデフォルトの 0 ではなく 1 から始まっています。表示用の番号として使いたい場合に便利です。

7.2 zip

zipは、複数のリストを同時にループ処理するための関数です。それぞれのリストから同じ位置の要素を組み合わせて処理できます。基本的な構文は以下のとおりです。

for 要素1, 要素2 in zip(リスト1, リスト2):
    # 要素1と要素2を使った処理

実際に試してみましょう。main.py に以下の内容を記述して実行してみましょう。このプログラムは、2つのリストを組み合わせて表示するものです。

names = ["田中", "鈴木", "佐藤"]
scores = [78, 92, 65]

for name, score in zip(names, scores):
    print(f"{name}: {score}点")

実行すると以下のように出力されます。

田中: 78点
鈴木: 92点
佐藤: 65点

zip(names, scores) により、namesscores の同じ位置の要素がペアで取り出されています。1回目のループで "田中"78、2回目で "鈴木"92、3回目で "佐藤"65 が組み合わされて表示されています。

3つ以上のリストを同時に処理することも可能です。

names = ["田中", "鈴木", "佐藤"]
scores = [78, 92, 65]
cities = ["東京", "大阪", "名古屋"]

for name, score, city in zip(names, scores, cities):
    print(f"{name}{city}): {score}点")

実行すると以下のように出力されます。

田中(東京): 78点
鈴木(大阪): 92点
佐藤(名古屋): 65点

zip(names, scores, cities) により、3つのリストの同じ位置の要素がまとめて取り出されています。各ループで名前・点数・都市が組み合わされ、「田中(東京): 78点」のように表示されています。

💡 ポイント
zip() はリストの長さが異なる場合、短い方に合わせて処理が終了します。全てのリストの長さを揃えておくか、長い方に合わせたい場合は itertools.zip_longest() を使用します。

8. まとめ

この講座では、Pythonの応用的な構文について学びました。

  • Noneは「値がない」ことを表す特別な値で、判定には is None / is not None を使う
  • 例外処理try / except / finally)でエラーを適切に処理できる
  • sys.argvでコマンドライン引数を受け取り、実行時にプログラムの動作を変えられる
  • リスト内包表記でリスト生成を簡潔に記述できる
  • ラムダ関数は名前のない小さな関数で、sorted() のキー指定などで活用できる
  • enumerateでインデックス付きのループ処理ができる
  • zipで複数のリストを同時にループ処理できる