Python入門:関数の定義と呼び出し

2025-10-22

はじめに

前回の講義ではループ処理について学びました。今回は、プログラムを構造化し、コードの再利用性を高めるための重要な概念である「関数」について学びます。関数を使うことで、同じコードを何度も書く必要がなくなり、プログラムの保守性や可読性が大幅に向上します。

関数の基本概念

関数は、特定のタスクを実行するためのコードのブロックです。一度定義すると、何度でも呼び出して使用することができます。これは、現実世界のレシピのようなものだと考えてください – 一度レシピを書けば、何度でもそれに従って料理を作ることができます。

関数を使用する利点

  1. コードの再利用: 同じ機能を何度も書く必要がなくなります
  2. 保守性の向上: 修正が必要な場合、1箇所を変更するだけで済みます
  3. 可読性の向上: 関数名が何を行うかを説明するため、コードが理解しやすくなります
  4. デバッグの容易さ: 問題の箇所を特定しやすくなります
  5. コードの整理: 複雑なプログラムを小さな部品に分割できます

関数の定義と呼び出し

基本的な関数の定義

Pythonではdefキーワードを使って関数を定義します。関数名と引数を指定し、コロンの後に処理を記述します。戻り値はreturn文で返します。引数や戻り値の型ヒントを追加することで、コードの可読性を高めることもできます。これにより、繰り返し使用する処理を効率的にまとめることができます。

def greet():
    """シンプルな挨拶関数"""
    print("Hello, World!")

# 関数の呼び出し
greet()  # Hello, World!

関数定義の構文

defキーワードで関数を定義し、括弧内にパラメータを記述します。三重引用符ではドキュメンテーション文字列(docstring)を記述し、関数の説明を加えます。

関数本体では必要な処理を実行し、return文で戻り値を返します。return文は省略可能で、省略した場合はNoneが返されます。これがPython関数の基本的な枠組みです。

def 関数名(パラメータ):
    """ドキュメンテーション文字列(docstring)"""
    # 関数の本体
    処理
    return 戻り値  # 省略可能

パラメータと引数

パラメータを持つ関数

greet関数はnameパラメータを受け取り、f-stringを用いて「Hello, {name}!」という挨拶文を画面に出力します。ドキュメンテーション文字列で関数の目的を明記しています。関数定義後、greet("Alice")およびgreet("Bob")で関数を呼び出しており、それぞれの名前を含んだ個別の挨拶メッセージが生成・表示されます。

def greet(name):
    """名前を受け取って挨拶する関数"""
    print(f"Hello, {name}!")

# 関数の呼び出し
greet("Alice")    # Hello, Alice!
greet("Bob")      # Hello, Bob!

複数のパラメータ

introduce関数は名前・年齢・居住地の3つのパラメータを取り、f-stringを使ってそれらを組み合わせた自己紹介文を生成し出力します。関数をintroduce("山田太郎", 25, "東京")と呼び出すことで、指定された情報が文章に埋め込まれ、「私は山田太郎です。25歳で東京に住んでいます。」という完全な自己紹介が表示されます。

def introduce(name, age, city):
    """自己紹介をする関数"""
    print(f"私は{name}です。{age}歳で{city}に住んでいます。")

# 関数の呼び出し
introduce("山田太郎", 25, "東京")
# 私は山田太郎です。25歳で東京に住んでいます。

デフォルトパラメータ

greet関数は必須のnameパラメータと、デフォルト値が"Hello"messageパラメータを受け取ります。greet("Alice")と呼び出すとデフォルト値が使用され"Hello, Alice!"と出力され、greet("Bob", "Hi")のように第二引数を指定すると、その値がメッセージとして使用され"Hi, Bob!"と出力されます。これにより、関数の柔軟性が高まります。

def greet(name, message="Hello"):
    """デフォルトメッセージを持つ挨拶関数"""
    print(f"{message}, {name}!")

# 関数の呼び出し
greet("Alice")              # Hello, Alice!
greet("Bob", "Hi")          # Hi, Bob!
greet("Charlie", "Welcome") # Welcome, Charlie!

キーワード引数

create_profile関数は必須のnameagecityパラメータと、デフォルト値が"Japan"countryパラメータを持ちます。キーワード引数を使用することで、引数の順序を自由に指定して関数を呼び出すことができます。また、位置引数とキーワード引数を混在させることも可能で、柔軟な関数呼び出しを実現しています。

def create_profile(name, age, city, country="Japan"):
    """プロファイルを作成する関数"""
    print(f"Name: {name}")
    print(f"Age: {age}")
    print(f"City: {city}")
    print(f"Country: {country}")
    print("-" * 20)

# キーワード引数を使った呼び出し
create_profile(name="Alice", age=25, city="Tokyo")
create_profile(age=30, city="Osaka", name="Bob", country="USA")
create_profile("Charlie", city="Kyoto", age=35)  # 混在も可能

戻り値

return文の使用

add関数は2つの数値を受け取り、それらを足し算した結果をreturn文で返します。戻り値は変数sum_resultに代入して後で使用できるほか、print関数内で直接使用することもできます。これにより、関数の計算結果をプログラムの別の部分で柔軟に利用でき、コードの再利用性と可読性が高まります。

def add(a, b):
    """2つの数値を足し算する関数"""
    result = a + b
    return result

# 戻り値の使用
sum_result = add(5, 3)
print(f"5 + 3 = {sum_result}")  # 5 + 3 = 8

# 直接使用も可能
print(f"10 + 15 = {add(10, 15)}")  # 10 + 15 = 25

複数の値を返す

calculate関数は2つの数値の四則演算結果をタプルとして返します。戻り値は単一の変数でタプルとして受け取ることも、複数の変数にアンパックして個別に受け取ることもできます。除算ではゼロ除算を防ぐため条件分岐を行っています。この仕組みにより、複数の計算結果を一度に返し、効率的に処理することが可能になります。

def calculate(a, b):
    """四則演算をすべて行う関数"""
    addition = a + b
    subtraction = a - b
    multiplication = a * b
    division = a / b if b != 0 else "undefined"

    return addition, subtraction, multiplication, division

# 複数の戻り値を受け取る
result = calculate(10, 5)
print(f"結果: {result}")  # 結果: (15, 5, 50, 2.0)

# 個別の変数で受け取る
add, sub, mul, div = calculate(8, 4)
print(f"足し算: {add}")  # 足し算: 12
print(f"引き算: {sub}")  # 引き算: 4
print(f"掛け算: {mul}")  # 掛け算: 32
print(f"割り算: {div}")  # 割り算: 2.0

変数のスコープ

ローカル変数とグローバル変数

global_varはグローバル変数として関数内外の両方でアクセス可能です。一方、local_varは関数内で定義されたローカル変数であり、関数内でのみ参照できます。関数外でlocal_varを参照しようとするとエラーが発生します。これにより、変数の有効範囲(スコープ)の概念が明確に示されています。

# グローバル変数
global_var = "I'm global"

def test_scope():
    # ローカル変数
    local_var = "I'm local"
    print(f"関数内: {local_var}")
    print(f"関数内: {global_var}")  # グローバル変数はアクセス可能

test_scope()
print(f"関数外: {global_var}")
# print(f"関数外: {local_var}")  # エラー: local_varは未定義

globalキーワード

関数increment内でglobal counterと宣言することで、関数外で定義されたグローバル変数counterを参照・更新できるようになります。これにより、関数を呼び出すたびにカウンターの値が1ずつ増加し、その状態が保持されます。通常、ローカルスコープからグローバル変数を変更するにはglobal宣言が必要です。

counter = 0

def increment():
    global counter  # グローバル変数を変更することを宣言
    counter += 1
    print(f"カウンター: {counter}")

increment()  # カウンター: 1
increment()  # カウンター: 2
increment()  # カウンター: 3

様々な関数のタイプ

再帰関数

次のfactorial関数は自身を呼び出すことで階乗を計算します。nが0または1の場合に1を返す終了条件を設け、それ以外の場合にはn * factorial(n-1)を返します。これにより、複雑な問題をより小さな同じ問題に分解して解決する再帰の仕組みを実現しています。例えば5!は5×4×3×2×1=120と計算されます。

def factorial(n):
    """階乗を計算する再帰関数"""
    if n == 0 or n == 1:
        return 1
    else:
        return n * factorial(n - 1)

# 使用例
print(f"5! = {factorial(5)}")    # 5! = 120
print(f"10! = {factorial(10)}")  # 10! = 3628800

ラムダ関数(無名関数)

lambda x: x ** 2は引数xを受け取りxの2乗を返す簡潔な関数定義です。通常の関数と同様の結果を得られますが、1行で記述できる利点があります。特にmap()関数など、関数を引数として渡す場面で有効で、リストの各要素に対して効率的に操作を適用できます。

# 通常の関数定義
def square(x):
    return x ** 2

# ラムダ関数
square_lambda = lambda x: x ** 2

# 使用例
print(square(5))          # 25
print(square_lambda(5))   # 25

# よく使われる場面
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared)  # [1, 4, 9, 16, 25]

ドキュメンテーション文字列

三重引用符で囲まれた部分に関数の目的、引数の詳細、戻り値の説明を記述しています。このdocstringは__doc__属性で直接参照できるほか、help()関数で詳細なヘルプとして表示されます。これによりコードの可読性が向上し、他の開発者が関数を理解しやすくなります。

def calculate_bmi(weight, height):
    """
    BMI(ボディマス指数)を計算します

    Parameters:
    weight (float): 体重(kg)
    height (float): 身長(m)

    Returns:
    float: BMI値
    """
    return weight / (height ** 2)

# ドキュメンテーションの確認
print(calculate_bmi.__doc__)
help(calculate_bmi)

実践的な関数の例

データ処理関数

analyze_numbers関数は数値のリストを受け取り、合計・平均・最大値・最小値を計算してタプルで返します。空のリストが渡された場合にはゼロ値で対応します。戻り値はアンパックして個別の変数で受け取り、フォーマットを整えて表示できます。実際のデータ分析で頻繁に使用される基本的な統計計算を効率的に行うことができます。

def analyze_numbers(numbers):
    """
    数値のリストを分析する関数

    Returns:
    tuple: (合計, 平均, 最大値, 最小値)
    """
    if not numbers:
        return 0, 0, 0, 0

    total = sum(numbers)
    average = total / len(numbers)
    maximum = max(numbers)
    minimum = min(numbers)

    return total, average, maximum, minimum

# 使用例
data = [23, 45, 67, 12, 89, 34, 56]
total, avg, max_val, min_val = analyze_numbers(data)

print(f"データ: {data}")
print(f"合計: {total}")
print(f"平均: {avg:.2f}")
print(f"最大値: {max_val}")
print(f"最小値: {min_val}")

文字列処理関数

format_name関数は姓と名を受け取り、オプションでミドルネームを含むフォーマットされた名前を返します。create_email関数は姓名からメールアドレスを生成し、デフォルトドメインを変更可能です。両関数ともデフォルト引数を活用して柔軟性を持たせており、実際のアプリケーションでよく見られる名前やメールアドレスの処理を効率的に行うことができます。

def format_name(first, last, middle=""):
    """名前をフォーマットする関数"""
    if middle:
        return f"{last} {first} {middle}"
    else:
        return f"{last} {first}"

def create_email(first, last, domain="company.com"):
    """メールアドレスを作成する関数"""
    return f"{first.lower()}.{last.lower()}@{domain}"

# 使用例
name1 = format_name("Taro", "Yamada")
name2 = format_name("Hanako", "Sato", "Yuki")
email1 = create_email("Taro", "Yamada")
email2 = create_email("Hanako", "Sato", "university.ac.jp")

print(name1)   # Yamada Taro
print(name2)   # Sato Hanako Yuki
print(email1)  # taro.yamada@company.com
print(email2)  # hanako.sato@university.ac.jp

バリデーション関数

validate_password関数はパスワードを受け取り、長さ、数字の有無、大文字・小文字の包含をチェックします。各条件を満たさない場合、Falseとエラーメッセージのタプルを返し、すべての条件を満たせばTrueと成功メッセージを返します。この設計により、関数の呼び出し元は検証結果と詳細なフィードバックの両方を一度に受け取ることができます。

def validate_password(password):
    """
    パスワードの強度を検証する関数

    Returns:
    tuple: (有効かどうか, エラーメッセージ)
    """
    if len(password) < 8:
        return False, "パスワードは8文字以上である必要があります"

    if not any(char.isdigit() for char in password):
        return False, "パスワードには少なくとも1つの数字を含めてください"

    if not any(char.isupper() for char in password):
        return False, "パスワードには少なくとも1つの大文字を含めてください"

    if not any(char.islower() for char in password):
        return False, "パスワードには少なくとも1つの小文字を含めてください"

    return True, "パスワードは有効です"

# 使用例
passwords = ["weak", "Weak123", "STRONG123", "Strong123"]

for pwd in passwords:
    is_valid, message = validate_password(pwd)
    status = "✅" if is_valid else "❌"
    print(f"{status} {pwd}: {message}")

高度な関数の機能

argsと*kwargs

*args**kwargs は、関数に可変個の引数を渡すための仕組みです。

  • *args:位置引数をまとめて受け取る(タプルになる)
  • **kwargs:キーワード引数をまとめて受け取る(辞書になる)

*argsで任意の数の位置引数をタプルとして、**kwargsで任意の数のキーワード引数を辞書として受け取ります。これにより、引数の数や種類が不定な状況でも柔軟に対応できる関数を定義できます。関数内ではそれぞれの引数をループ処理して内容を表示しており、多様な引数のパターンに対応可能な汎用的な関数の構造を示しています。

def flexible_function(*args, **kwargs):
    """可変長引数を受け取る関数"""
    print("位置引数:")
    for i, arg in enumerate(args):
        print(f"  {i}: {arg}")

    print("キーワード引数:")
    for key, value in kwargs.items():
        print(f"  {key}: {value}")

# 使用例
flexible_function(1, 2, 3, name="Alice", age=25, city="Tokyo")

関数を引数として渡す

apply_operation関数は数値のリストと操作関数を受け取り、リスト内包表記を使用して各要素に関数を適用します。これにより、square関数やdouble関数など、異なる操作を同じインターフェースで適用でき、コードの再利用性と柔軟性が大幅に向上します。関数を引数として渡すこの仕組みは、Pythonの関数型プログラミングの基本的なパターンです。

def apply_operation(numbers, operation):
    """数値のリストに関数を適用する"""
    return [operation(x) for x in numbers]

def square(x):
    return x ** 2

def double(x):
    return x * 2

# 使用例
numbers = [1, 2, 3, 4, 5]
squared = apply_operation(numbers, square)
doubled = apply_operation(numbers, double)

print(f"元の数値: {numbers}")
print(f"二乗: {squared}")  # [1, 4, 9, 16, 25]
print(f"2倍: {doubled}")   # [2, 4, 6, 8, 10]

デコレーター

デコレーターとは、関数やメソッドに追加の機能を簡単に付け加える仕組みです。「@」記号を使って関数の前に指定します。

timer関数は実行時間を計測するデコレーターで、@timerで修飾された関数は元の処理に加えて実行時間の計測機能が追加されます。内部ではwrapper関数が元の関数をラップし、実行前後の時間差を計算して表示します。デコレーターを使用することで、既存の関数を変更することなく機能を拡張できるため、コードの保守性と再利用性が向上します。

def timer(func):
    """関数の実行時間を計測するデコレーター"""
    import time

    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__}の実行時間: {end_time - start_time:.4f}秒")
        return result

    return wrapper

@timer
def slow_function():
    """時間がかかる関数の例"""
    import time
    time.sleep(2)
    return "完了"

# 使用例
result = slow_function()
print(result)

エラーハンドリングと関数

safe_divide関数はtry-exceptブロックを使用して、ゼロ除算エラーと型エラーを捕捉します。除算が成功すれば結果を返し、エラーが発生した場合は対応する日本語のエラーメッセージを返します。これによりプログラムが異常終了することなく、ユーザーフレンドリーな形でエラーを処理できます。実際のアプリケーションで重要な堅牢なコードの書き方を示しています。

def safe_divide(a, b):
    """ゼロ除算を防ぐ安全な割り算関数"""
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        return "エラー: 0で割ることはできません"
    except TypeError:
        return "エラー: 数値を入力してください"

# 使用例
print(safe_divide(10, 2))   # 5.0
print(safe_divide(10, 0))   # エラー: 0で割ることはできません
print(safe_divide(10, "a")) # エラー: 数値を入力してください

ベストプラクティス

1. 単一責任の原則

単一責任の原則を実践する関数設計の比較例です。悪い例ではデータ処理とファイル保存という異なる責任を1つの関数が担っています。良い例ではそれぞれの機能を独立した関数に分離し、process_dataはデータ処理のみを、save_to_fileはファイル保存のみを担当します。このように関数を小さく分割することで、コードの再利用性、テストの容易性、保守性が大幅に向上します。

# 悪い例 - 複数のことを行う関数
def process_data_and_save(data):
    # データ処理...
    # ファイル保存...
    pass

# 良い例 - 単一の責任を持つ関数
def process_data(data):
    # データ処理...
    return processed_data

def save_to_file(data, filename):
    # ファイル保存...
    pass

2. 説明的な関数名

関数命名の重要性を示す比較例です。悪い例calcは曖昧で具体的な処理内容がわかりません。良い例calculate_monthly_interestは関数の目的が明確で、引数の意味も理解しやすくなっています。適切な関数名と引数名を付けることで、コードの可読性と保守性が向上し、他の開発者が機能を理解しやすくなります。これがプロフェッショナルなコーディングの基本です。

# 悪い例
def calc(a, b):
    pass

# 良い例
def calculate_monthly_interest(principal, annual_rate):
    pass

3. 適切なドキュメンテーション

関数は温度値と変換元・変換先の単位を受け取り、適切な変換計算を行います。docstringにはパラメータの詳細な説明、戻り値の型、発生する可能性のある例外が明記されており、関数の使用方法が明確に理解できます。このような設計により、信頼性の高い再利用可能なユーティリティ関数を作成できます。実際の実装では、摂氏・華氏・ケルビン間の変換ロジックが追加されます。

def convert_temperature(value, from_unit, to_unit):
    """
    温度を異なる単位間で変換します

    Parameters:
    value (float): 変換する温度値
    from_unit (str): 元の単位 ('C', 'F', 'K')
    to_unit (str): 変換先の単位 ('C', 'F', 'K')

    Returns:
    float: 変換された温度値

    Raises:
    ValueError: 無効な単位が指定された場合
    """
    # 変換ロジック...
    pass

デバッグのコツ

関数の実行過程を可視化するために、入力値、中間計算結果、最終結果の各段階で詳細な情報を出力します。これはデバッグ時の基本的な手法ですが、本番コードでは適切なロギングライブラリを使用することが推奨されます。開発段階ではこのようなprint文が問題の特定に役立ちますが、完成後は削除またはコメントアウトするべきです。

def debug_function(x, y):
    """デバッグ用のprint文を含む関数"""
    print(f"入力: x={x}, y={y}")

    result = x * y
    print(f"中間結果: {result}")

    final_result = result + 10
    print(f"最終結果: {final_result}")

    return final_result

# 使用例
debug_function(5, 3)

まとめ

Pythonの関数の定義と呼び出しについて学びました。関数の基本的な構造や呼び出し方を理解し、パラメータと引数によるデータの受け渡し、戻り値による結果の返却方法を学びました。また、ローカル変数とグローバル変数のスコープの違い、再帰関数やラムダ関数といった多様な関数タイプ、ドキュメンテーションによる説明文の書き方も学習しました。

さらに、データ処理や文字列処理などの実践的な例を通して、*args**kwargs・デコレーターといった高度な機能や、例外処理による安全な関数設計、そして品質の高いコードを書くためのベストプラクティスについても習得しました。

関数はプログラミングの基本的な構成要素であり、適切に使うことでコードの再利用性、保守性、可読性が大幅に向上します。


練習問題

初級問題(3問)

問題1
2つの数値を受け取り、その和を返す関数add_numbersを作成してください。
関数を呼び出して結果を表示するプログラムを作成してください。

問題2
半径を受け取り、円の面積を計算して返す関数calculate_circle_areaを作成してください。
円周率は3.14とします。

問題3
名前と年齢を受け取り、「○○さんは××歳です」という文字列を返す関数create_introductionを作成してください。

中級問題(6問)

問題4
数値のリストを受け取り、その平均値を計算して返す関数calculate_averageを作成してください。

問題5
文字列を受け取り、その文字列が回文(前から読んでも後ろから読んでも同じ)かどうかを判定する関数is_palindromeを作成してください。

問題6
開始年と終了年を受け取り、その期間内のうるう年をすべてリストとして返す関数find_leap_yearsを作成してください。

問題7
数値のリストと閾値を受け取り、閾値以上の要素のみを含む新しいリストを返す関数filter_above_thresholdを作成してください。

問題8
秒数を受け取り、それを「時間:分:秒」の形式に変換して返す関数format_timeを作成してください(例:3665秒 → "1:01:05")。

問題9
3つの数値を受け取り、それらを辺とする三角形が成立するかどうかを判定する関数is_valid_triangleを作成してください。

上級問題(3問)

問題10
数値を受け取り、その数値が素数かどうかを判定する関数is_primeを作成してください。さらに、その関数を使用して、ある範囲内のすべての素数を求める関数find_primes_in_rangeも作成してください。

問題11
銀行口座を模拟するクラスのような振る舞いをする関数群を作成してください。

  • create_account(initial_balance): 初期残高で口座を作成
  • deposit(account, amount): 預け入れ
  • withdraw(account, amount): 引き出し
  • get_balance(account): 残高照会

問題12
簡単な電卓プログラムを関数を使用して作成してください。四則演算(+,-,*,/)を行う関数と、ユーザーインターフェースを処理する関数を分けて実装してください。ゼロ除算のエラー処理も含めてください。

関数の定義と呼び出し 練習問題 解答

初級問題 解答

問題1 解答

def add_numbers(a, b):
    """2つの数値の和を計算する"""
    return a + b

# 関数のテスト
result1 = add_numbers(5, 3)
result2 = add_numbers(10, 20)
result3 = add_numbers(-5, 8)

print(f"5 + 3 = {result1}")
print(f"10 + 20 = {result2}")
print(f"-5 + 8 = {result3}")

# ユーザー入力版
try:
    num1 = float(input("1つ目の数値を入力してください: "))
    num2 = float(input("2つ目の数値を入力してください: "))
    result = add_numbers(num1, num2)
    print(f"{num1} + {num2} = {result}")
except ValueError:
    print("数値を正しく入力してください")

実行結果:

5 + 3 = 8
10 + 20 = 30
-5 + 8 = 3

問題2 解答

def calculate_circle_area(radius):
    """円の面積を計算する"""
    pi = 3.14
    area = pi * radius ** 2
    return area

# 関数のテスト
radius1 = 5
radius2 = 10
radius3 = 2.5

area1 = calculate_circle_area(radius1)
area2 = calculate_circle_area(radius2)
area3 = calculate_circle_area(radius3)

print(f"半径{radius1}cmの円の面積: {area1:.2f}平方cm")
print(f"半径{radius2}cmの円の面積: {area2:.2f}平方cm")
print(f"半径{radius3}cmの円の面積: {area3:.2f}平方cm")

# ユーザー入力版
try:
    r = float(input("円の半径を入力してください (cm): "))
    if r >= 0:
        area = calculate_circle_area(r)
        print(f"半径{r}cmの円の面積は{area:.2f}平方cmです")
    else:
        print("半径は0以上の値を入力してください")
except ValueError:
    print("数値を正しく入力してください")

実行結果:

半径5cmの円の面積: 78.50平方cm
半径10cmの円の面積: 314.00平方cm
半径2.5cmの円の面積: 19.62平方cm

問題3 解答

def create_introduction(name, age):
    """名前と年齢から自己紹介文を作成する"""
    return f"{name}さんは{age}歳です"

# 関数のテスト
intro1 = create_introduction("山田太郎", 25)
intro2 = create_introduction("佐藤花子", 30)
intro3 = create_introduction("鈴木一郎", 18)

print(intro1)
print(intro2)
print(intro3)

# ユーザー入力版
name = input("お名前を入力してください: ")
try:
    age = int(input("年齢を入力してください: "))
    if age >= 0:
        introduction = create_introduction(name, age)
        print(introduction)
    else:
        print("年齢は0以上の値を入力してください")
except ValueError:
    print("年齢は数値で入力してください")

実行結果:

山田太郎さんは25歳です
佐藤花子さんは30歳です
鈴木一郎さんは18歳です

中級問題 解答

問題4 解答

def calculate_average(numbers):
    """数値のリストの平均値を計算する"""
    if not numbers:  # 空リストのチェック
        return 0

    total = sum(numbers)
    average = total / len(numbers)
    return average

# 関数のテスト
test_data1 = [1, 2, 3, 4, 5]
test_data2 = [10, 20, 30, 40, 50]
test_data3 = [2.5, 3.7, 1.8, 4.2]
test_data4 = []  # 空リスト

avg1 = calculate_average(test_data1)
avg2 = calculate_average(test_data2)
avg3 = calculate_average(test_data3)
avg4 = calculate_average(test_data4)

print(f"データ{test_data1}の平均: {avg1}")
print(f"データ{test_data2}の平均: {avg2}")
print(f"データ{test_data3}の平均: {avg3:.2f}")
print(f"空リストの平均: {avg4}")

# 発展版:統計情報をすべて返す関数
def calculate_statistics(numbers):
    """数値のリストから統計情報を計算する"""
    if not numbers:
        return 0, 0, 0, 0

    total = sum(numbers)
    average = total / len(numbers)
    maximum = max(numbers)
    minimum = min(numbers)

    return total, average, maximum, minimum

# 統計情報関数のテスト
data = [23, 45, 67, 12, 89, 34]
total, avg, max_val, min_val = calculate_statistics(data)
print(f"\nデータ: {data}")
print(f"合計: {total}, 平均: {avg:.2f}, 最大値: {max_val}, 最小値: {min_val}")

実行結果:

データ[1, 2, 3, 4, 5]の平均: 3.0
データ[10, 20, 30, 40, 50]の平均: 30.0
データ[2.5, 3.7, 1.8, 4.2]の平均: 3.05
空リストの平均: 0

データ: [23, 45, 67, 12, 89, 34]
合計: 270, 平均: 45.00, 最大値: 89, 最小値: 12

問題5 解答

def is_palindrome(text):
    """文字列が回文かどうかを判定する"""
    # 大文字小文字を区別しない、空白や記号を無視
    cleaned_text = ''.join(char.lower() for char in text if char.isalnum())
    return cleaned_text == cleaned_text[::-1]

# 関数のテスト
test_cases = [
    "たけやぶやけた",
    "racecar",
    "A man a plan a canal Panama",
    "hello",
    "12321",
    "not a palindrome"
]

for text in test_cases:
    result = is_palindrome(text)
    status = "✅ 回文です" if result else "❌ 回文ではありません"
    print(f"'{text}' → {status}")

# 詳細バージョン
def is_palindrome_detailed(text):
    """回文判定の詳細バージョン"""
    cleaned_text = ''.join(char.lower() for char in text if char.isalnum())
    is_pal = cleaned_text == cleaned_text[::-1]

    print(f"元の文字列: '{text}'")
    print(f"処理後: '{cleaned_text}'")
    print(f"反転後: '{cleaned_text[::-1]}'")
    print(f"結果: {'回文です' if is_pal else '回文ではありません'}")
    print("-" * 40)

    return is_pal

# 詳細バージョンのテスト
print("\n詳細バージョン:")
is_palindrome_detailed("たけやぶやけた")
is_palindrome_detailed("Hello World")

実行結果:

'たけやぶやけた' → ✅ 回文です
'racecar' → ✅ 回文です
'A man a plan a canal Panama' → ✅ 回文です
'hello' → ❌ 回文ではありません
'12321' → ✅ 回文です
'not a palindrome' → ❌ 回文ではありません

詳細バージョン:
元の文字列: 'たけやぶやけた'
処理後: 'たけやぶやけた'
反転後: 'たけやぶやけた'
結果: 回文です
----------------------------------------
元の文字列: 'Hello World'
処理後: 'helloworld'
反転後: 'dlrowolleh'
結果: 回文ではありません
----------------------------------------

問題6 解答

def is_leap_year(year):
    """うるう年かどうかを判定する"""
    return (year % 400 == 0) or (year % 100 != 0 and year % 4 == 0)

def find_leap_years(start_year, end_year):
    """指定期間内のうるう年をリストで返す"""
    leap_years = []
    for year in range(start_year, end_year + 1):
        if is_leap_year(year):
            leap_years.append(year)
    return leap_years

# 関数のテスト
leap_years_2000_2020 = find_leap_years(2000, 2020)
leap_years_1900_1920 = find_leap_years(1900, 1920)

print("2000年から2020年までのうるう年:")
print(leap_years_2000_2020)
print(f"数: {len(leap_years_2000_2020)}年")

print("\n1900年から1920年までのうるう年:")
print(leap_years_1900_1920)
print(f"数: {len(leap_years_1900_1920)}年")

# ユーザー入力版
try:
    start = int(input("開始年を入力してください: "))
    end = int(input("終了年を入力してください: "))

    if start <= end:
        leap_years = find_leap_years(start, end)
        if leap_years:
            print(f"\n{start}年から{end}年までのうるう年:")
            for i, year in enumerate(leap_years, 1):
                print(f"{i:2d}. {year}年")
            print(f"合計: {len(leap_years)}年")
        else:
            print("該当するうるう年はありません")
    else:
        print("開始年は終了年以下で入力してください")

except ValueError:
    print("数値を正しく入力してください")

実行結果:

2000年から2020年までのうるう年: [2000, 2004, 2008, 2012, 2016, 2020]
数: 6年

1900年から1920年までのうるう年: [1904, 1908, 1912, 1916, 1920]
数: 5年

問題7 解答

def filter_above_threshold(numbers, threshold):
    """閾値以上の要素のみをフィルタリングする"""
    filtered_list = []
    for number in numbers:
        if number >= threshold:
            filtered_list.append(number)
    return filtered_list

# リスト内包表記を使った簡潔なバージョン
def filter_above_threshold_v2(numbers, threshold):
    """閾値以上の要素のみをフィルタリング(リスト内包表記使用)"""
    return [number for number in numbers if number >= threshold]

# 関数のテスト
numbers = [23, 45, 12, 67, 34, 89, 5, 78]
thresholds = [30, 50, 20]

for threshold in thresholds:
    filtered = filter_above_threshold(numbers, threshold)
    filtered_v2 = filter_above_threshold_v2(numbers, threshold)

    print(f"元のリスト: {numbers}")
    print(f"閾値 {threshold} 以上: {filtered}")
    print(f"フィルタ後: {filtered_v2}")
    print(f"該当する要素数: {len(filtered)}")
    print("-" * 50)

# 発展版:さまざまなフィルタ条件
def filter_numbers(numbers, condition_func):
    """条件関数に基づいてフィルタリングする"""
    return [number for number in numbers if condition_func(number)]

# さまざまなフィルタ条件のテスト
test_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 偶数だけフィルタリング
even_numbers = filter_numbers(test_numbers, lambda x: x % 2 == 0)
print(f"偶数: {even_numbers}")

# 5より大きい数だけフィルタリング
greater_than_five = filter_numbers(test_numbers, lambda x: x > 5)
print(f"5より大きい数: {greater_than_five}")

# 平方数だけフィルタリング
perfect_squares = filter_numbers(test_numbers, lambda x: (x ** 0.5).is_integer())
print(f"平方数: {perfect_squares}")

実行結果:

元のリスト: [23, 45, 12, 67, 34, 89, 5, 78]
閾値 30 以上: [45, 67, 34, 89, 78]
フィルタ後: [45, 67, 34, 89, 78]
該当する要素数: 5
--------------------------------------------------
元のリスト: [23, 45, 12, 67, 34, 89, 5, 78]
閾値 50 以上: [67, 89, 78]
フィルタ後: [67, 89, 78]
該当する要素数: 3
--------------------------------------------------
元のリスト: [23, 45, 12, 67, 34, 89, 5, 78]
閾値 20 以上: [23, 45, 67, 34, 89, 78]
フィルタ後: [23, 45, 67, 34, 89, 78]
該当する要素数: 6
--------------------------------------------------
偶数: [2, 4, 6, 8, 10]
5より大きい数: [6, 7, 8, 9, 10]
平方数: [1, 4, 9]

問題8 解答

def format_time(total_seconds):
    """秒数を時間:分:秒の形式に変換する"""
    if total_seconds < 0:
        return "無効な時間です"

    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = total_seconds % 60

    return f"{hours:02d}:{minutes:02d}:{seconds:02d}"

# 関数のテスト
test_seconds = [3665, 12345, 60, 3599, 86400, 0]

for seconds in test_seconds:
    formatted = format_time(seconds)
    print(f"{seconds:6d}秒 = {formatted}")

# 詳細情報も返すバージョン
def format_time_detailed(total_seconds):
    """秒数を詳細な形式に変換する"""
    if total_seconds < 0:
        return "無効な時間です", {}

    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    seconds = total_seconds % 60

    formatted = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
    details = {
        'hours': hours,
        'minutes': minutes,
        'seconds': seconds,
        'total_seconds': total_seconds
    }

    return formatted, details

# 詳細バージョンのテスト
print("\n詳細バージョン:")
test_seconds_detailed = [3665, 12345]
for sec in test_seconds_detailed:
    formatted, details = format_time_detailed(sec)
    print(f"{sec}秒 = {formatted}")
    print(f"  詳細: {details['hours']}時間 {details['minutes']}分 {details['seconds']}秒")

実行結果:

3665秒 = 01:01:05
12345秒 = 03:25:45
60秒 = 00:01:00
3599秒 = 00:59:59
86400秒 = 24:00:00
0秒 = 00:00:00

詳細バージョン:
3665秒 = 01:01:05
詳細: 1時間 1分 5秒
12345秒 = 03:25:45
詳細: 3時間 25分 45秒

問題9 解答

def is_valid_triangle(a, b, c):
    """3辺が三角形を形成できるかどうかを判定する"""
    # すべて正の数かチェック
    if a <= 0 or b <= 0 or c <= 0:
        return False, "すべての辺は正の数である必要があります"

    # 三角形の成立条件をチェック
    condition1 = (a + b) > c
    condition2 = (a + c) > b
    condition3 = (b + c) > a

    if condition1 and condition2 and condition3:
        # 三角形の種類も判定
        if a == b == c:
            triangle_type = "正三角形"
        elif a == b or a == c or b == c:
            triangle_type = "二等辺三角形"
        else:
            triangle_type = "不等辺三角形"
        return True, triangle_type
    else:
        return False, "三角形を形成できません"

# 関数のテスト
test_triangles = [
    (3, 4, 5),
    (5, 5, 5),
    (5, 5, 8),
    (1, 2, 3),
    (0, 4, 5),
    (2, 2, 4)
]

print("三角形の成立判定:")
for a, b, c in test_triangles:
    is_valid, message = is_valid_triangle(a, b, c)
    status = "✅" if is_valid else "❌"
    print(f"辺({a}, {b}, {c}) → {status} {message}")

# ユーザー入力版
try:
    print("\n三角形の判定(ユーザー入力)")
    side_a = float(input("辺aの長さを入力してください: "))
    side_b = float(input("辺bの長さを入力してください: "))
    side_c = float(input("辺cの長さを入力してください: "))

    is_valid, message = is_valid_triangle(side_a, side_b, side_c)

    if is_valid:
        print(f"✅ これらの辺で三角形を作成できます → {message}")
    else:
        print(f"❌ {message}")

except ValueError:
    print("数値を正しく入力してください")

実行結果:

三角形の成立判定:
辺(3, 4, 5) → ✅ 不等辺三角形
辺(5, 5, 5) → ✅ 正三角形
辺(5, 5, 8) → ✅ 二等辺三角形
辺(1, 2, 3) → ❌ 三角形を形成できません
辺(0, 4, 5) → ❌ すべての辺は正の数である必要があります
辺(2, 2, 4) → ❌ 三角形を形成できません

上級問題 解答

問題10 解答

def is_prime(n):
    """数値が素数かどうかを判定する"""
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False

    # 3から√nまでの奇数で割り切れるかチェック
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0:
            return False

    return True

def find_primes_in_range(start, end):
    """指定範囲内のすべての素数をリストで返す"""
    primes = []
    for number in range(start, end + 1):
        if is_prime(number):
            primes.append(number)
    return primes

# 関数のテスト
print("素数判定のテスト:")
test_numbers = [2, 3, 4, 17, 25, 29, 100]
for num in test_numbers:
    result = is_prime(num)
    status = "素数" if result else "素数ではない"
    print(f"{num:3d} → {status}")

print("\n範囲内の素数検索:")
primes_1_50 = find_primes_in_range(1, 50)
primes_100_150 = find_primes_in_range(100, 150)

print(f"1から50までの素数: {primes_1_50}")
print(f"数: {len(primes_1_50)}個")
print(f"100から150までの素数: {primes_100_150}")
print(f"数: {len(primes_100_150)}個")

# ユーザー入力版
try:
    start_range = int(input("\n検索開始値を入力してください: "))
    end_range = int(input("検索終了値を入力してください: "))

    if start_range <= end_range:
        primes = find_primes_in_range(start_range, end_range)

        if primes:
            print(f"\n{start_range}から{end_range}までの素数:")
            # 10個ずつ表示
            for i in range(0, len(primes), 10):
                chunk = primes[i:i+10]
                for prime in chunk:
                    print(f"{prime:4d}", end=" ")
                print()
            print(f"\n合計: {len(primes)}個の素数が見つかりました")
        else:
            print("該当する素数はありません")
    else:
        print("開始値は終了値以下で入力してください")

except ValueError:
    print("数値を正しく入力してください")

実行結果:

素数判定のテスト:
2 → 素数
3 → 素数
4 → 素数ではない
17 → 素数
25 → 素数ではない
29 → 素数
100 → 素数ではない

範囲内の素数検索:
1から50までの素数: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
数: 15個
100から150までの素数: [101, 103, 107, 109, 113, 127, 131, 137, 139, 149]
数: 10個

問題11 解答

def create_account(initial_balance=0):
    """銀行口座を作成する"""
    if initial_balance < 0:
        raise ValueError("初期残高は0以上である必要があります")

    account = {
        'balance': initial_balance,
        'transaction_history': []
    }

    # 初期残高の記録
    if initial_balance > 0:
        account['transaction_history'].append(f"口座開設: +{initial_balance}円")

    return account

def deposit(account, amount):
    """口座に預け入れする"""
    if amount <= 0:
        raise ValueError("預入額は0より大きい必要があります")

    account['balance'] += amount
    account['transaction_history'].append(f"預入: +{amount}円")
    return account['balance']

def withdraw(account, amount):
    """口座から引き出す"""
    if amount <= 0:
        raise ValueError("引出額は0より大きい必要があります")

    if amount > account['balance']:
        raise ValueError("残高不足です")

    account['balance'] -= amount
    account['transaction_history'].append(f"引出: -{amount}円")
    return account['balance']

def get_balance(account):
    """現在の残高を取得する"""
    return account['balance']

def get_transaction_history(account):
    """取引履歴を取得する"""
    return account['transaction_history']

# 銀行口座システムのテスト
print("=== 銀行口座システムのテスト ===")

# 口座作成
account1 = create_account(1000)
print(f"口座開設: 初期残高 {get_balance(account1)}円")

# 預入
new_balance = deposit(account1, 500)
print(f"500円預入: 残高 {new_balance}円")

# 引出
new_balance = withdraw(account1, 300)
print(f"300円引出: 残高 {new_balance}円")

# 複数回の取引
deposit(account1, 1000)
withdraw(account1, 200)
deposit(account1, 1500)

print(f"\n最終残高: {get_balance(account1)}円")

# 取引履歴の表示
print("\n取引履歴:")
history = get_transaction_history(account1)
for i, transaction in enumerate(history, 1):
    print(f"{i:2d}. {transaction}")

# エラーハンドリングのテスト
print("\n=== エラーハンドリングのテスト ===")
account2 = create_account(100)

try:
    withdraw(account2, 200)  # 残高不足
except ValueError as e:
    print(f"エラー: {e}")

try:
    deposit(account2, -50)  # 不正な預入額
except ValueError as e:
    print(f"エラー: {e}")

try:
    create_account(-100)  # 不正な初期残高
except ValueError as e:
    print(f"エラー: {e}")

実行結果:

=== 銀行口座システムのテスト ===
口座開設: 初期残高 1000円
500円預入: 残高 1500円
300円引出: 残高 1200円

最終残高: 3500円

取引履歴:
1. 口座開設: +1000円
2. 預入: +500円
3. 引出: -300円
4. 預入: +1000円
5. 引出: -200円
6. 預入: +1500円

=== エラーハンドリングのテスト ===
エラー: 残高不足です
エラー: 預入額は0より大きい必要があります
エラー: 初期残高は0以上である必要があります

問題12 解答

def add(a, b):
    """足し算"""
    return a + b

def subtract(a, b):
    """引き算"""
    return a - b

def multiply(a, b):
    """掛け算"""
    return a * b

def divide(a, b):
    """割り算(ゼロ除算チェック付き)"""
    if b == 0:
        raise ValueError("0で割ることはできません")
    return a / b

def get_number_input(prompt):
    """数値入力を受け取る"""
    while True:
        try:
            return float(input(prompt))
        except ValueError:
            print("数値を正しく入力してください")

def get_operation_input():
    """演算子入力を受け取る"""
    valid_operations = ['+', '-', '*', '/']
    while True:
        operation = input("演算子を選択してください (+, -, *, /): ").strip()
        if operation in valid_operations:
            return operation
        else:
            print("無効な演算子です。+, -, *, / のいずれかを入力してください")

def calculator():
    """電卓プログラムのメイン関数"""
    print("=== シンプル電卓 ===")
    print("終了するには 'quit' と入力してください")

    while True:
        print("\n" + "="*30)

        # 終了確認
        user_input = input("計算を続けますか? (続ける: Enter, 終了: quit): ").strip().lower()
        if user_input == 'quit':
            print("電卓を終了します")
            break

        try:
            # 数値と演算子の入力
            num1 = get_number_input("1つ目の数値を入力してください: ")
            operation = get_operation_input()
            num2 = get_number_input("2つ目の数値を入力してください: ")

            # 計算の実行
            if operation == '+':
                result = add(num1, num2)
            elif operation == '-':
                result = subtract(num1, num2)
            elif operation == '*':
                result = multiply(num1, num2)
            elif operation == '/':
                result = divide(num1, num2)

            # 結果の表示
            print(f"\n計算結果: {num1} {operation} {num2} = {result}")

        except ValueError as e:
            print(f"エラー: {e}")
        except Exception as e:
            print(f"予期せぬエラーが発生しました: {e}")

# 電卓の起動
if __name__ == "__main__":
    calculator()

# 単体テスト関数
def test_calculator_operations():
    """電卓機能の単体テスト"""
    print("\n=== 単体テスト ===")

    test_cases = [
        (5, 3, '+', 8),
        (10, 4, '-', 6),
        (6, 7, '*', 42),
        (15, 3, '/', 5),
    ]

    for a, b, op, expected in test_cases:
        if op == '+':
            result = add(a, b)
        elif op == '-':
            result = subtract(a, b)
        elif op == '*':
            result = multiply(a, b)
        elif op == '/':
            result = divide(a, b)

        status = "✅ PASS" if result == expected else "❌ FAIL"
        print(f"{a} {op} {b} = {result} (期待値: {expected}) {status}")

    # ゼロ除算のテスト
    try:
        divide(10, 0)
        print("❌ FAIL: ゼロ除算の例外が発生しませんでした")
    except ValueError:
        print("✅ PASS: ゼロ除算の例外が正しく発生しました")

# 単体テストの実行
test_calculator_operations()

実行結果:

=== シンプル電卓 ===
終了するには 'quit' と入力してください

==============================
1つ目の数値を入力してください: 10
演算子を選択してください (+, -, *, /): *
2つ目の数値を入力してください: 5

計算結果: 10.0 * 5.0 = 50.0
==============================

1つ目の数値を入力してください: 20
演算子を選択してください (+, -, *, /): /
2つ目の数値を入力してください: 0
エラー: 0で割ることはできません

=== 単体テスト ===
5 + 3 = 8 (期待値: 8) ✅ PASS
10 - 4 = 6 (期待値: 6) ✅ PASS
6 * 7 = 42 (期待値: 42) ✅ PASS
15 / 3 = 5.0 (期待値: 5) ✅ PASS
✅ PASS: ゼロ除算の例外が正しく発生しました