🎯 この章で学ぶこと

  • def による関数の定義と呼び出し
  • 引数(入力)と戻り値(出力)
  • デフォルト引数とキーワード引数
  • 変数のスコープ(有効範囲)
  • ドキュメント文字列(docstring)

5.1 関数とは — 処理の部品化

関数とは「入力を受け取り、決まった処理をして、結果を返す」部品です。def(defineの略)で定義します。

def greet(name):
    """名前を受け取って挨拶文を返す"""
    return f"こんにちは、{name} さん"

message = greet("tanaka")   # 呼び出し
print(message)              # こんにちは、tanaka さん
print(greet("suzuki"))      # こんにちは、suzuki さん

5.2 なぜ関数にするのか

「パスワードが規則を満たすかチェックする」処理を例に考えます。もしこの判定ロジックをコピペで10か所に書いたら、規則が変わったとき10か所すべてを直す必要があり、1か所でも直し忘れたらそこが穴になります

def is_valid_password(password):
    """パスワードが最低限の要件を満たすか判定する"""
    if len(password) < 12:
        return False
    if password.lower() == password:   # 大文字が1つもない
        return False
    if password.upper() == password:   # 小文字が1つもない
        return False
    return True

print(is_valid_password("abc"))                 # False
print(is_valid_password("CorrectHorse2026"))    # True
🛡️ セキュリティの視点 — 検証ロジックは一元化する

セキュリティ上重要な処理(入力検証、認可チェック、パスワード判定など)は、必ず1つの関数にまとめて全員がそれを使うのが鉄則です。同じチェックがあちこちに散らばっていると、修正漏れ・実装のばらつきが生まれ、そこが攻撃者の突破口になります。「Single Point of Truth(真実は1か所に)」と覚えてください。

5.3 複数の引数・デフォルト引数・キーワード引数

def lock_account(username, threshold=5, notify=True):
    """失敗回数がしきい値以上ならロック判定する(教材用の枠組み)"""
    print(f"{username}: しきい値={threshold}, 通知={notify}")

lock_account("tanaka")                       # デフォルト値が使われる
lock_account("suzuki", 3)                    # 位置で渡す
lock_account("sato", notify=False)           # 名前で渡す(キーワード引数)
lock_account("yamada", threshold=10, notify=False)
tanaka: しきい値=5, 通知=True
suzuki: しきい値=3, 通知=True
sato: しきい値=5, 通知=False
yamada: しきい値=10, 通知=False
💡 キーワード引数は読みやすさの武器

lock_account("sato", 5, False) より lock_account("sato", threshold=5, notify=False) のほうが、後から読む人(3ヶ月後の自分を含む)に意図が伝わります。

5.4 戻り値を活用する

関数は値を返せるので、「判定する関数」と「判定結果を使う側」を分離できます。複数の値を返すこともできます(実体はタプルです)。

def analyze_log(lines):
    """ログの行リストから (総数, 失敗数) を返す"""
    failed = 0
    for line in lines:
        if "FAILED" in line:
            failed += 1          # failed = failed + 1 の短縮形
    return len(lines), failed

logs = [
    "LOGIN OK user=tanaka",
    "LOGIN FAILED user=admin",
    "LOGIN FAILED user=root",
]
total, failed = analyze_log(logs)   # 2つの戻り値を同時に受け取る
print(f"総件数 {total} 件中、失敗 {failed} 件")
💡 += という書き方

failed += 1failed = failed + 1 と同じ意味の短縮形です。頻出なのでここで覚えてしまいましょう。-=*= などもあります。

5.5 変数のスコープ — どこから見えるか

関数の中で作った変数は、その関数の中でしか使えません。これを「ローカル変数」と呼びます。

def check():
    result = "OK"     # ローカル変数
    return result

check()
print(result)         # NameError! 関数の外からは見えない

逆に、関数の外で定義した変数(グローバル変数)は関数の中から読めますが、安易に頼るとどこで値が変わったのか追えないコードになります。原則は「関数が必要とするものは引数で渡し、結果は戻り値で返す」です。

✅ スコープを狭く保つ=影響範囲を狭く保つ

変数の見える範囲を最小にする設計は、セキュリティの「最小権限の原則」のコード版です。影響範囲が狭いほど、バグも脆弱性も入り込みにくく、見つけやすくなります。

5.6 docstring — 関数の説明書

関数の先頭に """...""" で書く説明を docstring と呼びます。help() で参照でき、チーム開発では事実上の必須マナーです。

def mask_email(email):
    """メールアドレスの@より前を伏せ字にして返す。

    例: mask_email("tanaka@example.com") → "t*****@example.com"
    """
    local, domain = email.split("@")
    return local[0] + "*" * (len(local) - 1) + "@" + domain

print(mask_email("tanaka@example.com"))   # t*****@example.com

この mask_email のような「個人情報を伏せる」処理は、ログを安全に扱うための実務頻出パターンです。

まとめ

練習問題

問題 5-1

摂氏温度を華氏に変換する関数 to_fahrenheit(celsius) を書いてください(華氏 = 摂氏 × 9 ÷ 5 + 32)。to_fahrenheit(25)77.0 になれば正解です。

解答を見る
def to_fahrenheit(celsius):
    """摂氏を華氏に変換する"""
    return celsius * 9 / 5 + 32

print(to_fahrenheit(25))   # 77.0
問題 5-2

5.2の is_valid_password に「数字を1文字以上含むこと」という条件を追加してください。ヒント:文字列 s の各文字 c について c.isdigit() で数字かどうか判定できます。

解答を見る
def is_valid_password(password):
    """パスワードが要件(12文字以上・大小英字・数字)を満たすか判定する"""
    if len(password) < 12:
        return False
    if password.lower() == password:
        return False
    if password.upper() == password:
        return False
    has_digit = False
    for c in password:
        if c.isdigit():
            has_digit = True
    if not has_digit:
        return False
    return True

print(is_valid_password("CorrectHorse"))       # False(数字なし)
print(is_valid_password("CorrectHorse2026"))   # True

慣れてきたら any(c.isdigit() for c in password) と1行でも書けます。

問題 5-3

IPアドレスの文字列を受け取り、「プライベートIPかどうか」をおおまかに判定する関数 is_private_ip(ip) を書いてください。10.192.168.172.16. のいずれかで始まればプライベートとみなして構いません。ヒント:startswith()

解答を見る
def is_private_ip(ip):
    """プライベートIPアドレスらしいかを簡易判定する(教材用)"""
    return (ip.startswith("10.")
            or ip.startswith("192.168.")
            or ip.startswith("172.16."))

print(is_private_ip("192.168.1.5"))   # True
print(is_private_ip("8.8.8.8"))       # False

実務では標準ライブラリ ipaddress モジュールの ip_address(ip).is_private を使うとより正確です(第7章参照)。「すでにある安全な部品を使う」のもセキュアコーディングの基本です。