演習形式で学ぶPythonプログラミング vol.17 ~関数の戻り値~

# まえがき
Python3.13環境を想定した演習問題を扱っています。変数からオブジェクト指向まできちんと扱います。続編として、データサイエンティストのためのPython(NumPyやpandasのような定番ライブラリ編)や統計学・統計解析・機械学習・深層学習・SQLをはじめCS全般の内容も扱う予定です。

対象者は、[1]を一通り読み終えた人、またそれに準ずる知識を持つ人とします。ただし初心者の方も、文法学習と同時並行で進めることで、文法やPythonそれ自体への理解が深まるような問題を選びました。前者については、答えを見るために考えてみることをお勧めしますが、後者については、自身と手元にある文法書を読みながら答えを考えるというスタイルも良いと思います。章分けについては[1]および[2]を参考にしました。

ストックしている問題のみすべて公開するのもありかなと考えたのですが、あくまで記事として読んでもらうことを主眼に置き、1記事1トピック上限5問に解答解説を付与するという方針で進めます。すべての問題を提供しない分、自分のストックの中から、特に読むに値するものを選んだつもりです。
また内容は、ただ文法をそのままコーディング問題にするのではなく、文法を俯瞰してなおかつ実務でも応用できるものを目指します。前者については世の中にあふれていますし、それらはすでに完成されたものです(例えばPython Vtuberサプーさんの動画[3]など)。これらを解くことによって得られるものも多いですし、そのような学習もとても有効ですが、私が選んだものからも得られるものは多いと考えます。
私自身がPython初心者ですので、誤りや改善点などがあればご指摘いただけると幸いです。
**➡ 最近複数の方から、誤りのご指摘、編集リクエストによる修正/改良をいただいております。この場を借りてですが、感謝申し上げます。**

今回は「ユーザー定義関数の戻り値」について扱います。それでは始めましょう！


# Q.17-1
**関数の戻り値を変数に格納せずに連鎖的に利用する設計（例：`print(sorted(get_data()))`）の可読性と拡張性を評価せよ。**

### 問題背景
#### 【1】関数の「戻り値の連鎖利用」とは
Pythonにおいて関数は戻り値（return値）を持つオブジェクトであり、それを直ちに他の関数の引数に渡す構文がしばしば用いられる。たとえば以下のようなコード：
```python
print(sorted(get_data()))
```
このような記述では、関数 `get_data()` の戻り値が即座に `sorted()` に渡され、さらにその結果が `print()` に渡される。これは関数の連鎖的利用（function chaining）と呼ばれる。

#### 【2】利点と欠点
・利点（簡潔性と即時性）
中間変数を使わずに記述できるため、コードが短くなる
一時的な処理や副作用のない関数に対して有効
パイプライン的な処理を簡潔に表現できる

・欠点（可読性と拡張性）
中間結果を参照・再利用できない
処理の各段階に名前をつけられないため、意味が見えにくくなる
デバッグや単体テストが困難になる
処理を後で変更・挿入する際に、全体を見直す必要がある

したがって、状況に応じた使い分けが求められる。

### 解答例とコードによる実践
```python
# 【1】仮のデータを取得する関数（例：データベースやAPIから取得）
def get_data():  # 【1】
    return ["banana", "apple", "cherry"]  # 【2】リストを返す

# 【3】構文1：関数の戻り値を変数に格納せずに連鎖的に利用（簡潔だが拡張性は低い）
print("=== Inline chaining ===")  # 【3】
print(sorted(get_data()))  # 【4】get_data() の戻り値を直接 sorted() に渡し、結果を print する

# 【5】構文2：中間結果に名前をつけて可読性と拡張性を高めた形式
print("\n=== Named intermediate steps ===")  # 【5】
data = get_data()  # 【6】get_data() の結果を変数 data に格納
sorted_data = sorted(data)  # 【7】data を sorted に渡し、結果を別名で保持
print(sorted_data)  # 【8】整形済みのリストを出力
```
```: 実行結果
=== Inline chaining ===
['apple', 'banana', 'cherry']

=== Named intermediate steps ===
['apple', 'banana', 'cherry']
```

### 本問のまとめ
Pythonにおける関数の戻り値の連鎖的利用は、構文上の簡潔性という利点を持つ一方で、可読性・拡張性・保守性においては中間変数による分割記述に劣る場合がある。以下に、評価軸ごとの比較を示す：
| 評価軸  | 連鎖的記述（Chaining） | 分割記述（Named Steps） |
| ---- | --------------- | ----------------- |
| 簡潔さ  | 高い              | 低い（行数が増える）        |
| 可読性  | 低い（複雑になりやすい）    | 高い（処理単位が明確）       |
| 拡張性  | 低い（挿入・変更に弱い）    | 高い（処理単位で変更可）      |
| 再利用性 | 低い（再呼び出し不可）     | 高い（途中結果を使える）      |

結論として、スクリプトのような一時的処理や非常に単純な関数連鎖では chaining は有効だが、中〜大規模の処理・保守対象のコードでは、中間変数を用いた段階的処理のほうが適していると評価される。

# Q.17-2
**複数の `return` がある関数において、それらが返す値の型が異なるとき、呼び出し側でどのような対応が必要かを記述せよ。**

### 問題背景
#### 【1】Python関数は異なる型を返すことが可能である
Pythonの関数は、複数の `return` 文を含み、異なる状況に応じて異なる型の値を返すことができる。これは動的型付け言語であるPythonの柔軟性の一つであるが、呼び出し側で戻り値の型に応じた処理を行わないと、エラーやバグの温床になりうる。

```python
def f(x):
    if x > 0:
        return x * 2    # int型
    else:
        return "error"  # str型
```
このような関数の呼び出し元では、`f()` が `int` を返すか `str` を返すかを事前に予測し、対応する処理を設計する必要がある。

#### 【2】想定される問題と対処方法
| 課題                 | 典型的な失敗例                            | 解決策                            |
| ------------------ | ---------------------------------- | ------------------------------ |
| 型が異なると処理が失敗する      | `"error" + 1` は `TypeError` を引き起こす | `isinstance()` による判定           |
| IDEや型チェッカーが警告を出さない | Pythonでは静的型付けがないため、型誤りを見逃しやすい      | `typing` モジュールによる型注釈           |
| 処理の分岐が煩雑になる        | 多くの `if` 文で型ごとに処理が分かれて読みにくい        | 明確な `return` ポリシーと `Union` の導入 |

#### 【3】推奨される呼び出し側の設計方針
・ `isinstance()` による戻り値の型判定
・ 戻り値に `"status"` や `"type"` などのメタ情報を埋め込んで分岐
・ 型ヒント（`Union`, `Optional`）による明示的な設計

### 解答例とコードによる実践
```python
def process_input(data):
    # 【1】dataが数値ならば2乗して返す
    if isinstance(data, (int, float)):  # 【1】dataが数値型(intまたはfloat)であるかを確認する
        return data ** 2  # 【2】dataの2乗を計算して返す（型: intまたはfloat）

    # 【3】dataがリストなら、長さを返す
    elif isinstance(data, list):  # 【3】dataがリストであるかを確認する
        return len(data)  # 【4】リストの要素数を返す（型: int）

    # 【5】それ以外ならエラーメッセージを返す
    else:
        return {"error": "Unsupported type"}  # 【5】辞書型でエラー情報を返す（型: dict）

# 呼び出し側の処理（各戻り値の型に応じて分岐）

inputs = [5, [1, 2, 3], "hello"]  # 【6】int型、list型、str型の3つの異なる入力を用意する

for item in inputs:  # 【7】各入力に対してループを行う
    result = process_input(item)  # 【8】関数を呼び出し、戻り値をresultに格納する

    # 【9】戻り値の型ごとに処理を分岐する
    if isinstance(result, (int, float)):  # 【9】戻り値が数値であれば
        print(f"数値結果: {result}")  # 【10】数値として出力する

    elif isinstance(result, dict) and "error" in result:  # 【11】エラー辞書であれば
        print(f"エラー: {result['error']}")  # 【12】エラーメッセージを出力する

    else:
        print(f"その他の出力: {result}")  # 【13】それ以外のケース（リストの長さなど）を出力する
```
```: 実行結果
数値結果: 25
数値結果: 3
エラー: Unsupported type
```

### まとめ
・ 関数が返す値の型が複数ある場合は、呼び出し側で明確な型判定と分岐処理を行うことが必要である。
・ Pythonでは `isinstance()` を使って柔軟に型判定ができ、さらに `dict` を返すことで処理のメタ情報も同時に提供できる。
・ `typing.Union` による型注釈は、開発者にとってのドキュメントとなり、IDEによる補完や静的解析を助ける。
・ 型が複数になるような関数設計を行う場合、戻り値の統一性と明確な仕様の共有が保守性を高める鍵である。

# Q.17-3
**関数が副作用を含む処理を行う場合、その戻り値の有無と意味を明確化する設計方針を述べよ。**

### 問題背景
#### 【1】副作用を持つ関数とは何か
Pythonにおける副作用（side effect）とは、「関数が外部の状態を変化させる処理」を指す。これは純粋関数（pure function）の対義概念である。

純粋関数の特徴
・同じ入力に対して必ず同じ出力を返す
・関数の外部に影響を及ぼさない（副作用を持たない）

副作用を持つ関数の例
・ファイルやデータベースへの書き込み
・グローバル変数の変更
・ユーザーへの通知（print, log, alert）
・外部APIの呼び出し

副作用は現実のプログラムでは避けて通れない。しかし、副作用が「成功したか」「何を起こしたか」が分からないとバグの原因となる。

#### 【2】副作用を含む関数における戻り値の設計指針
| 戻り値の形式         | 意味                  | 利点                      |
| -------------- | ------------------- | ----------------------- |
| `None`         | 出力の必要がない副作用（例：ログ）   | 処理が意図的に完了したことを示す        |
| `bool`         | 成否だけを示す             | 呼び出し側で簡潔に分岐可能           |
| `str` や `dict` | 成否 + エラー情報 + 副作用の内容 | ロギング・エラーハンドリングと組み合わせやすい |
| カスタムクラス        | 状態と結果をオブジェクトとして保持   | 可読性・拡張性が高く、設計が疎結合になりやすい |

副作用の結果に意味を持たせたいときは戻り値を利用する。逆に、意図的に「戻り値に依存させたくない」処理なら `None` を明示的に返すことが良い設計とされる。

#### 【3】副作用と戻り値の設計原則（ベストプラクティス）
・副作用に成功したかどうかを必ず戻り値で伝える
・副作用の内容（影響対象、処理結果）を必要に応じて構造化して返す
・処理失敗の詳細を戻り値または例外で渡す（ログやUI表示へ）
・副作用処理を「値の返却」と混在させない設計を優先する（副作用関数を別に切り出す）

### 解答例とコードによる実践
#### 【A】基礎的なコード例
```python
# 副作用（ファイル書き込み）と戻り値の両立設計を示す
import os

def save_user_data(username: str, score: int, file_path: str = "user_data.txt") -> bool:
    # 【1】関数名や引数で、ファイル書き込みが行われることが明示されている
    try:
        # 【2】指定されたファイルに書き込みを行う（副作用）
        with open(file_path, "w") as f:
            f.write(f"{username},{score}\n")  # 【3】CSV形式で保存する
        return True  # 【4】成功した場合はTrueを返す
    except Exception as e:
        print(f"エラー発生: {e}")  # 【5】エラー内容を表示（副作用）
        return False  # 【6】失敗時はFalseを返す

# 呼び出し側の設計：戻り値に応じて処理を分岐

# 【1】ユーザー名とスコアを定義する
user = "alice"  # 【1】
score = 85  # 【2】

# 【3】関数を呼び出し、副作用とともに成功可否を受け取る
result = save_user_data(user, score)  # 【3】

# 【4】戻り値によって結果の確認と処理の分岐を行う
if result:  # 【4】戻り値がTrue（成功）なら
    print("データの保存に成功しました")  # 【5】
else:
    print("データの保存に失敗しました")  # 【6】
```
```: 実行結果(成功時)
データの保存に成功しました
```
```: 実行結果(失敗時)
エラー発生: [Errno 13] Permission denied: '/root/user_data.txt'
データの保存に失敗しました
```

#### 【B】応用的なコード例
```python
# ユーザーデータをJSONファイルに保存する処理
# 成功すれば保存先パスと保存件数を返す
# 失敗すれば error フィールドを含む辞書を返す
# 保存は副作用、戻り値は処理の意味を表す

# 【1】必要な標準ライブラリをインポートする（ファイル操作、日付、型ヒントに必要）
import json  # JSON形式でデータを保存するための標準ライブラリ
import os  # ファイルパス操作やディレクトリ作成のための標準ライブラリ
from datetime import datetime  # 現在時刻を取得してファイル名に使うためのモジュール
from typing import Union, Dict  # 関数の戻り値に型ヒントを付けるためのライブラリ

# 【2】ユーザーリストをJSON形式で保存する関数を定義する
def save_users_to_json(users: list[dict], directory: str = "output") -> Union[Dict[str, Union[str, int]], Dict[str, str]]:
    # 【3】保存先のディレクトリが存在しない場合、新しく作成する
    if not os.path.exists(directory):  # 指定ディレクトリが存在するかを確認
        os.makedirs(directory)  # 存在しなければディレクトリを作成する（副作用）

    # 【4】現在時刻を使って一意なファイル名を作成する（重複防止のため）
    filename = f"users_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"  # 日付+時刻入りのファイル名
    path = os.path.join(directory, filename)  # ディレクトリとファイル名を結合して完全パスを作成

    try:
        # 【5】ファイルを開いて書き込みモードにする（エンコーディングはUTF-8）
        with open(path, "w", encoding="utf-8") as f:  # 書き込み用にファイルを開く
            # 【6】ユーザーリストをJSON形式でファイルに保存する（副作用）
            json.dump(users, f, ensure_ascii=False, indent=2)  # 読みやすい形式で書き込み

        # 【7】正常に保存できたら、保存パスと保存件数を含む辞書を返す
        return {
            "status": "success",  # 成功を示すフラグ
            "saved_path": path,  # 保存先ファイルのパス
            "user_count": len(users)  # 保存されたユーザー数
        }
    except Exception as e:
        # 【8】ファイル保存中に例外が発生した場合はエラーメッセージ付きの辞書を返す
        return {
            "status": "error",  # 失敗を示すフラグ
            "error": str(e)  # 発生した例外の内容を文字列として格納
        }

# 【9】保存するユーザーのリストを定義する
users = [  # ユーザー情報を辞書として定義
    {"name": "Alice", "score": 90},  # 1人目のユーザー
    {"name": "Bob", "score": 75}     # 2人目のユーザー
]

# 【10】定義したユーザーリストをJSONファイルに保存し、その結果を取得する
result = save_users_to_json(users)  # 関数を呼び出して保存を試みる

# 【11】戻り値の "status" を確認し、成功かエラーかで処理を分岐する
if result.get("status") == "success":  # 正常に保存できた場合
    # 【12】保存成功時は、保存件数と保存先のパスを表示する
    print(f"{result['user_count']}件のユーザーを保存しました: {result['saved_path']}")
else:
    # 【13】保存失敗時は、エラーメッセージを表示する
    print(f"保存に失敗しました: {result['error']}")
```
```: 実行結果(成功時)
2件のユーザーを保存しました: output/users_20250507_153322.json
```
```: 実行結果(失敗時)
保存に失敗しました: [Errno 13] Permission denied: 'output/...'
```

### まとめ
・関数が副作用（例：ファイル書き込み）を含む場合、戻り値はその実行結果の明示的な「報告書」であるべきである。
・`None` を返すだけの設計では、呼び出し側が処理の成否を把握できず、再現困難なバグの原因となる。
・Pythonicな設計では、処理の意図と結果を戻り値で表し、副作用を明確に制御することが好まれる。
・拡張性が高い設計として、戻り値を辞書化またはクラス化し、状態・メタ情報・成否を構造化する手法が有効である。


# Q.17-4
**関数の戻り値を後続処理で `if` 条件判定に利用する場合、真偽値以外の戻り値（リストや空文字列など）がどう評価されるかを説明せよ。**

### 問題背景
#### 【1】Python における if 判定の基本
Pythonの if 文では、式の**真偽値（boolean value）**に基づいて条件を評価する。つまり、
```python
if 条件式:
    # True のときに実行される
```
ここで「条件式」が `True` であると判断されるためには、暗黙の型変換（truth value testing）において真と判定される必要がある。

#### 【2】「真偽値ではない値」の評価基準（truthy / falsy）
Pythonでは、真偽値（`True`, `False`）以外の値も、その値の“空”かどうかなどに応じて暗黙的に `bool` 型に変換される。

| データ型         | 値           | `if` での評価  |
| ------------ | ----------- | ---------- |
| 数値 (`int`)   | `0`         | `False`（偽） |
| 数値 (`int`)   | `非ゼロ`（例：42） | `True`（真）  |
| 文字列 (`str`)  | `""`（空文字列）  | `False`（偽） |
| 文字列 (`str`)  | `"abc"`     | `True`（真）  |
| リスト (`list`) | `[]`（空リスト）  | `False`（偽） |
| リスト (`list`) | `[1, 2]`    | `True`（真）  |
| None         | `None`      | `False`（偽） |

この仕様により、関数が戻り値として空のリストや空文字列を返した場合、それを `if` に渡すと `False` とみなされる。

#### 【3】この評価が問題を引き起こす例
以下のようなコードを見てみよう。
```python
def get_items():
    return []

if get_items():
    print("アイテムがあります")
else:
    print("アイテムは空です")
```
このコードは `"アイテムは空です"` を出力する。
つまり、リストが空であることだけを判定しているにもかかわらず、`if` の構文上は明示的に `len(items) == 0` などと書かなくても済む。

#### 【4】なぜ注意が必要なのか
`0`, `[]`, `""`, `None` などはすべて `False` 扱いになるため、意図しない分岐が発生することがある。
特に `0` や `""` は意味のある戻り値（例：文字列処理や個数カウント）であるにもかかわらず、誤って `False` と判定してしまう危険性がある。
したがって、戻り値を `if` 条件に使う場合、その型と意味が適切に意図されているかを確認することが極めて重要である。

### 解答例とコードによる実践
以下のコードでは、関数 `analyze_data` が異なる入力に対して異なる型（リスト、文字列、None）を返し、それを `if` 条件でどのように評価すべきかを検証している。

```python
# 【1】関数を定義する（与えられたデータに応じて戻り値の型を変える）
def analyze_data(data):
    # 【2】もしデータがリスト型であり、かつ空でなければ
    if isinstance(data, list) and data:  # isinstanceで型チェックし、空でないことを確認
        return data  # 【3】そのままリストを返す（非空なので truthy として扱われる）

    # 【4】もしデータが文字列であり、stripで前後の空白を除いた後に空でなければ
    elif isinstance(data, str) and data.strip():  # 空白だけの文字列は除外される
        return f"Received string: {data.strip()}"  # 【5】加工した文字列を返す

    # 【6】それ以外のすべてのケース（None、数値、空リスト、空白文字列など）では
    else:
        return None  # 【7】明示的に None を返す（これは falsy）

# 【8】評価対象の入力データをリストで定義する
inputs = [  # いろいろな型と値を含むリスト
    [1, 2, 3],     # 【9】非空リスト：truthy
    [],            # 【10】空リスト：falsy
    "hello",       # 【11】非空文字列：truthy
    "     ",       # 【12】空白文字列（strip後は空）：falsy
    None,          # 【13】None：falsy
    0,             # 【14】数値（0）：falsy
]

# 【15】各入力を順番に処理するため、enumerateでループ処理を行う
for i, val in enumerate(inputs):  # iはインデックス、valはリスト中の要素
    result = analyze_data(val)  # 【16】関数 analyze_data を呼び出して戻り値を取得

    print(f"[{i+1}] 入力: {repr(val)}")  # 【17】入力値をそのままrepr形式（型もわかりやすい）で表示

    # 【18】戻り値が "真" と評価されるかどうかで分岐
    if result:  # Pythonでは空文字列・空リスト・None・0 などは "偽"（False）と評価される
        print(f"  → 条件判定: 真（True）として扱われた")  # 【19】条件が満たされた場合の表示
        print(f"  → 処理結果: {result}")  # 【20】関数からの戻り値を表示
    else:
        print(f"  → 条件判定: 偽（False）として扱われた")  # 【21】条件が満たされなかった場合の表示
        print(f"  → 処理結果: {result}")  # 【22】Noneや空値などの結果を表示

    print("-" * 50)  # 【23】各入力ごとの出力を見やすく区切る
```
```: 実行結果
[1] 入力: [1, 2, 3]
  → 条件判定: 真（True）として扱われた
  → 処理結果: [1, 2, 3]
--------------------------------------------------
[2] 入力: []
  → 条件判定: 偽（False）として扱われた
  → 処理結果: None
--------------------------------------------------
[3] 入力: 'hello'
  → 条件判定: 真（True）として扱われた
  → 処理結果: Received string: hello
--------------------------------------------------
[4] 入力: '     '
  → 条件判定: 偽（False）として扱われた
  → 処理結果: None
--------------------------------------------------
[5] 入力: None
  → 条件判定: 偽（False）として扱われた
  → 処理結果: None
--------------------------------------------------
[6] 入力: 0
  → 条件判定: 偽（False）として扱われた
  → 処理結果: None
--------------------------------------------------
```

### まとめ
・Pythonにおいて `if` 文は真偽値だけでなく、任意のオブジェクトを暗黙的に評価する。
・`[]` や `""` や `0` や `None` など、「空」や「ゼロ」の状態を持つ値は `False` として扱われる。
・この仕様により、関数の戻り値を `if` に直接使う場合は、暗黙の `bool()` 評価が意図と合致するか確認する必要がある。
・より安全に書くには、次のように 明示的に型チェックや長さ判定を行う方が可読性と保守性に優れる：
```python
if result is not None and isinstance(result, list) and len(result) > 0:
    ...
```

# Q.17-5
**`yield` を含む関数と `return` を含む関数の違いを、戻り値の観点から比較し、ジェネレータとの違いを示せ。**

### 問題背景
#### 【1】`return` は関数の「一度限りの終了と値の返却」
通常の関数において `return` は、「値を返して処理を終了する」という意味である。
`return` 文を実行した瞬間に関数の実行は停止し、それ以降の処理は一切実行されない。
`return` は1つの値（またはタプル、リストなど1つのオブジェクト）を返す。

```python
def f():
    return 42
```
この関数は `f()` を呼び出すと `42` を返すが、返す値は1回限りで、それ以降の処理はない。

#### 【2】`yield` は「値の逐次的な返却（遅延評価）」を行う
`yield` は、関数を一時停止して値を返すことを意味する。関数の状態を中断して保持する。
`yield` を含む関数を呼び出すと、即座には関数本体は実行されず、ジェネレータオブジェクトが返る。
`for` ループや `next()` を使ってそのジェネレータを評価すると、1つずつ値が生成される。

```python
def g():
    yield 1
    yield 2
```
この関数は 逐次的に複数の値を返すことができるため、大量のデータ処理においてパフォーマンスとメモリ効率の面で非常に有用である。

#### 【3】ジェネレータの特性
`yield` を含む関数を評価すると、ジェネレータ（generator）オブジェクトが生成される。
ジェネレータは イテレータ（iterator）であり、`__iter__()` と `__next__()` を持つ。
状態が保存されるため、「次にどこから再開するか」が記録される。

```python
gen = g()       # ジェネレータオブジェクトが返る（実行はされていない）
next(gen)       # 1 を返す。内部状態は 2 の位置で停止している
```

#### 【4】主な違いの比較表
| 観点    | `return`          | `yield`               |
| ----- | ----------------- | --------------------- |
| 処理の終了 | その場で終了（後続処理なし）    | 状態を一時停止（再開可能）         |
| 戻り値   | 単一値（複数値でも1オブジェクト） | イテレータとして複数の値を逐次返す     |
| メモリ使用 | すべての値を一度に生成       | 1つずつ遅延生成（メモリ効率が良い）    |
| 使われ方  | 通常の関数             | for文、next関数、ジェネレータ式など |


### 解答例とコードによる実践
以下では、「正規表現によって文字列から数字だけを抽出し、それを `return` と `yield` で処理する2通りの関数を比較」する。

```python
import re  # 【1】正規表現（文字列パターン抽出）を使うために re モジュールをインポート

# 【2】return を使って、全ての数字を一括で返す関数を定義する
def extract_numbers_return(text):  # text: 入力された文字列
    # 【3】正規表現 \d+ にマッチする「1文字以上の数字」を全てリストで取得する
    numbers = re.findall(r"\d+", text)  # 例：['42', '85', '0']
    # 【4】リスト内の各要素を int に変換し、新しいリストとして返す
    return [int(n) for n in numbers]  # 例：[42, 85, 0]

# 【5】yield を使って、数字を1つずつ返すジェネレータ関数を定義する
def extract_numbers_yield(text):  # text: 入力された文字列
    # 【6】re.finditer は、マッチする箇所を1つずつ逐次的に返すイテレータを生成
    for match in re.finditer(r"\d+", text):  # 繰り返し各マッチを取得
        # 【7】マッチした文字列を整数に変換し、yield で1つずつ返す（関数は中断されず再開可能）
        yield int(match.group())

# 【8】処理対象の文字列を定義する（数字を含む文章）
sample = "User A has 42 points, user B has 85 points, and user C has 0."

# 【9】extract_numbers_return を呼び出して、リストとして全数字を一括抽出
numbers_return = extract_numbers_return(sample)  # [42, 85, 0]

# 【10】return版の結果を表示する（まとめて出力される）
print("【return使用】一括抽出の結果:", numbers_return)

# 【11】yield版の結果を出力する準備（printの途中まで出力）
print("【yield使用】逐次抽出の結果:", end=" ")

# 【12】extract_numbers_yield を呼び出して、1つずつ数字を取り出しながら表示
for number in extract_numbers_yield(sample):  # 42 → 85 → 0
    print(number, end=" ")  # 数字をスペース区切りで出力

# 【13】ループ後に改行を入れて出力を整える
print()
```
```: 実行結果
【return使用】一括抽出の結果: [42, 85, 0]
【yield使用】逐次抽出の結果: 42 85 0
```

### まとめ
`return` は値を一度にまとめて返すのに対し、`yield` は処理の中断と再開を繰り返しながら値を1つずつ返す仕組みである。
`yield` を用いた関数はジェネレータ（遅延評価されるイテレータ）を返すため、特に「大量データ」「逐次処理」に適している。
メモリ消費やパフォーマンスの観点からは、一度にすべて処理する `return` に対して、`yield` は必要なときに必要なだけ処理できるという利点がある。
`yield` の理解には「状態が関数内に保持される」「`for` 文や `next` で実行される」点を踏まえることが重要である。

# Q.17-6
**戻り値の型が常に一定でない関数（例：成功時は数値、失敗時は None）に対して、安全に使うための呼び出し設計を論ぜよ。**

### 問題背景
#### 【1】Pythonは動的型付けである
Pythonでは、関数の戻り値に型を指定する義務がなく、ある場合は `int` を返し、別の条件では `None` を返すといった設計が簡単に可能である：

```python
def get_score(name):
    if name in db:
        return 100  # 数値
    else:
        return None  # 失敗時
```
このような関数設計は柔軟だが、戻り値の型が状況によって変化するため、呼び出し側が安全に取り扱わないとバグや例外の原因となる。

#### 【2】型が一定でない関数の使用リスク
| 戻り値         | 呼び出し側での危険な使い方                | 発生するエラー     |
| ----------- | ---------------------------- | ----------- |
| `None`      | `score + 5` のような算術処理を行う      | `TypeError` |
| `int`       | 正常系として処理可能                   | 問題なし        |
| `None` を見逃す | `if not score:` などで `0` と誤判定 | 意図しないロジック破綻 |

#### 【3】安全な呼び出し設計とは何か
以下のような構造を持つことが望ましい：

・型判定（`is None`）を明示的に行う
・`Optional[int]` を使って型ヒントを明示する
・`try-except` で `None` に対する操作を防ぐ
・共通のエラーハンドリング戦略を持つ

### 解答例とコードによる実践
「社員IDから給与を取得するが、存在しない場合は `None` を返す」というコードを考える

```python
from typing import Optional  # 【1】関数の戻り値に「int または None」の型ヒントを与えるために Optional をインポートする

# 【2】給与データベースを辞書として定義する（キー：社員ID、値：基本給）
salary_db = {
    "emp001": 5000,  # 【3】社員 emp001 の基本給は 5000 円
    "emp002": 6200,  # 【4】社員 emp002 の基本給は 6200 円
    "emp003": 4800   # 【5】社員 emp003 の基本給は 4800 円
}

# 【6】給与取得用の関数を定義する（該当がなければ None を返す）
def get_salary(emp_id: str) -> Optional[int]:  # emp_id は文字列型で受け取り、戻り値は int または None
    return salary_db.get(emp_id)  # 【7】辞書の get を使うと、キーが存在しない場合に None を返す

# 【8】確認対象とする社員IDのリストを作成する（存在するIDと存在しないIDを混ぜる）
emp_ids = ["emp001", "emp004", "emp002", "emp999"]  # emp004, emp999 は未登録なので None が返る

# 【9】リスト内の各社員IDに対してループ処理を行う（i はインデックス、emp_id は値）
for i, emp_id in enumerate(emp_ids):  # 【10】
    salary = get_salary(emp_id)  # 【11】関数を呼び出して給与情報を取得する

    print(f"[{i+1}] 社員ID: {emp_id}")  # 【12】現在処理している社員IDを表示する

    # 【13】戻り値が None（未登録）かどうかを明示的に判定する
    if salary is None:
        print("  → 給与データが存在しません")  # 【14】未登録の場合はその旨を出力する
    else:
        # 【15】salary が int として使えるため、年収見込みも計算できる
        print(f"  → 基本給: {salary} 円、年収見込: {salary * 12} 円")  # 12ヶ月分を掛けて年収を算出

    print("-" * 50)  # 【16】見やすくするための区切りを出力
```
```: 実行結果
[1] 社員ID: emp001
  → 基本給: 5000 円、年収見込: 60000 円
--------------------------------------------------
[2] 社員ID: emp004
  → 給与データが存在しません
--------------------------------------------------
[3] 社員ID: emp002
  → 基本給: 6200 円、年収見込: 74400 円
--------------------------------------------------
[4] 社員ID: emp999
  → 給与データが存在しません
--------------------------------------------------
```

#### 【補足：より厳密な型安全性の実現】
Python 3.10以降では、`match` 文（構造的パターンマッチ）を用いて、`None` 判定をより明示的に行うことができる：

```python
from typing import Optional  # 【1】関数の戻り値として「int または None」があることを示すため、Optionalをインポート

# 【2】給与データベースを辞書で定義する（社員IDをキー、給与を値とする）
salary_db = {
    "emp001": 5000,  # 【3】社員 emp001 の基本給は 5000 円
    "emp002": 6200,  # 【4】社員 emp002 の基本給は 6200 円
    "emp003": 4800   # 【5】社員 emp003 の基本給は 4800 円
}

# 【6】指定された社員IDの給与を取得する関数を定義する
def get_salary(emp_id: str) -> Optional[int]:  # emp_id は文字列、戻り値は int または None
    return salary_db.get(emp_id)  # 【7】辞書の .get() を使って、存在しないキーなら None を返す

# 【8】確認したい社員IDをリストで定義する（登録済・未登録の混在）
emp_ids = ["emp001", "emp004", "emp002", "emp999"]  # emp004, emp999 は salary_db に存在しない

# 【9】各社員IDに対してループ処理を行う（インデックスとIDを取得）
for i, emp_id in enumerate(emp_ids):  # 【10】
    salary = get_salary(emp_id)  # 【11】get_salary関数を呼び出し、給与（またはNone）を取得

    print(f"[{i+1}] 社員ID: {emp_id}")  # 【12】現在処理中の社員IDを表示

    # 【13】match文（構造的パターンマッチ）で salary の型・内容に応じて分岐処理を行う
    match salary:  # Python 3.10 以降で使用可能な構文
        case int():  # 【14】salary が int 型であればこのブロックに入る
            print(f"  → 基本給: {salary} 円、年収見込: {salary * 12} 円")  # 【15】年収見込みも計算して表示
        case None:  # 【16】salary が None の場合（つまり給与データが存在しない）
            print("  → 給与データが存在しません")  # 【17】未登録メッセージを表示
        case _:  # 【18】上記のどれにも該当しない場合（例：意図しない型）
            print("  → 不明な戻り値の型です")  # 【19】エラーハンドリング的に表示

    print("-" * 50)  # 【20】次の出力との区切り線を出力して視認性を高める
```

また、静的型チェッカー（`mypy`）で `Optional[int]` を利用すれば、Noneチェックを行わずに int として使用しようとした場合に警告が出るようにできる。

## Q.17-7
**戻り値が関数オブジェクトである場合、それを f()() のように連鎖的に使う構造の意味と使用例を記述せよ。**

### 問題背景
#### 【1】関数も「第一級オブジェクト（first-class object）」である
Pythonでは、関数は「第一級オブジェクト」として扱われ、変数に代入したり、引数として渡したり、戻り値として返すことができる。
```python
def outer():
    def inner():
        return "Hello"
    return inner  # ← 呼び出すのではなく「関数そのもの」を返す
```
上記の `outer()` を呼び出すと、戻り値は `"Hello"` ではなく、`inner` という関数そのものになる。
このような構造では、`outer()()` のように連鎖的に呼び出すことが可能となる。

#### 【2】構造的に f()() はどう解釈されるのか
```python
result = f()()
```
この式は次のように解釈される：
1. `f()`：最初の関数 `f` を呼び出して関数オブジェクトを返す
2. `()`：その戻ってきた関数オブジェクトをさらに呼び出す

つまり `f()()` は「関数を返す関数を呼び出して、その返された関数をさらに呼び出す」という2段階の処理である。

#### 【3】この構造の活用例
・クロージャの生成
・関数カスタマイズのファクトリ
・関数デコレータ
・引数を遅延的に与えるカリー化
などの高度な設計に用いられる。

### 解答例とコードによる実践
以下の例では、「あいさつ言語を切り替え可能な関数ファクトリ」を設計し、`f("ja")()` のようにして日本語あいさつを得るなど、関数オブジェクトの連鎖的な利用を表現する。

```python
# 【1】ファクトリ関数 greeting_factory を定義する（言語コードに応じて関数オブジェクトを返す）
def greeting_factory(lang: str):  # lang は "ja", "en", などの言語コードを受け取る
    # 【2】日本語の挨拶を返す関数 greet_ja を定義する（内側の関数）
    def greet_ja():  # 呼び出されたとき "こんにちは！" を返す
        return "こんにちは！"  # 【3】実行時にこの文字列を返す

    # 【4】英語の挨拶を返す関数 greet_en を定義する
    def greet_en():
        return "Hello!"  # 【5】英語の挨拶

    # 【6】その他の言語コードに対応するためのデフォルト関数を定義する
    def greet_default():
        return "Unsupported language."  # 【7】サポート外の言語に対するメッセージ

    # 【8】lang の値に応じて、適切な挨拶関数（greet_ja, greet_en など）を返す
    if lang == "ja":
        return greet_ja  # 【9】greet_ja 関数オブジェクトを返す（実行していない点に注意）
    elif lang == "en":
        return greet_en  # 【11】greet_en 関数オブジェクトを返す
    else:
        return greet_default  # 【12】どれにも当てはまらなければデフォルト関数を返す

# 【13】日本語の挨拶関数オブジェクトを取得し、ja_greeter に代入
ja_greeter = greeting_factory("ja")  # greeting_factory が greet_ja を返すので、ja_greeter は関数オブジェクト

# 【14】英語の挨拶関数オブジェクトを取得し、en_greeter に代入
en_greeter = greeting_factory("en")  # greet_en 関数が返る

# 【15】未知の言語コードに対してはデフォルトの挨拶関数が返る
unknown_greeter = greeting_factory("xx")  # greet_default 関数が返る

# 【16】ja_greeter（関数）を実行して、挨拶メッセージを表示する
print(ja_greeter())  # greet_ja() を呼び出して "こんにちは！" を出力

# 【17】en_greeter（関数）を実行して、挨拶メッセージを表示する
print(en_greeter())  # greet_en() を呼び出して "Hello!" を出力

# 【18】unknown_greeter（関数）を実行して、対応外のメッセージを表示する
print(unknown_greeter())  # greet_default() を呼び出して "Unsupported language." を出力

# 【19】関数の戻り値が関数であるため、greeting_factory("ja")() のように連鎖的に呼び出すことができる
print(greeting_factory("ja")())  # greeting_factory → greet_ja → "こんにちは！"

# 【20】英語バージョンの連鎖呼び出し
print(greeting_factory("en")())  # greeting_factory → greet_en → "Hello!"

# 【21】未知の言語バージョンの連鎖呼び出し
print(greeting_factory("xx")())  # greeting_factory → greet_default → "Unsupported language."
```
```: 実行結果
こんにちは！
Hello!
Unsupported language.
こんにちは！
Hello!
Unsupported language.
```

### 本問のまとめ
・Pythonにおいて `f()()` の構造は、「関数を返す関数」の連鎖呼び出しを意味する。
・この構造は、高階関数、関数ファクトリ、クロージャ、カリー化、デコレータなどの設計に用いられる高度な技法である。
・`f()` で返されるのは「関数オブジェクト」であり、返された関数に `()` をつけることで、その本体を実行するという二段階評価が行われる。
・この設計は、柔軟なAPI設計、動的な振る舞い切り替え、共通処理の抽象化などに非常に有用である。

## 【補足事項】
### 【1】`lambda` 式との組み合わせによる `f()()` 構造
`lambda` 式は無名関数（名前を持たない関数）を定義する構文であり、関数オブジェクトを即座に返すのに適している。
`lambda` を使うことで、関数オブジェクトを返す関数を簡潔に書ける。
`f = lambda: lambda: "Hello"` とすれば `f()()` の形で呼び出せる。

#### コード例：`lambda` による `f()()` 構造
```python
# 【1】外側のlambdaが関数オブジェクト（内側のlambda）を返す
f = lambda: (lambda: "Lambda連鎖!")  # 【1】

# 【2】最初の()で外側のlambdaを実行し、内側のlambdaを得る
# 【3】次の()で内側のlambdaを実行し、結果の文字列を得る
print(f()())  # 【2】【3】
```
```: 実行結果
Lambda連鎖!
```

### 【2】lambdaを活用したパラメータ付きクロージャ（`f(x)(y)`）
`f(x)` が関数を返し、それを `f(x)(y)` の形で呼び出す構造は、「引数を段階的に与えるカリー化」とも呼ばれる。
`lambda` を使うとこの構造をシンプルに記述できる。

#### コード例：2段階で引数を渡す加算関数
```python
# 【1】xを受け取るlambda式が、さらにyを受け取ってx + yを返すlambdaを返す
add = lambda x: (lambda y: x + y)  # 【1】

# 【2】add(10) → lambda y: 10 + y
# 【3】そのlambdaに5を渡す → 10 + 5 = 15
print(add(10)(5))  # 【2】【3】→ 15 を出力
```
```: 実行結果
15
```

### 【3】デコレータとの応用パターン：関数の実行をログ出力付きでラップする
デコレータは「関数を引数として受け取り、別の関数を返す関数」である。つまり、関数の戻り値が関数オブジェクト（＝高階関数）となる構造であり、`f()()` の延長にある。

`@decorator` 構文は `func = decorator(func)` の糖衣構文である。

#### コード例：関数の前後にログを出力するデコレータ
```python
# 【1】デコレータ関数を定義する（引数に関数を取り、ラップして新しい関数を返す）
def with_logging(func):  # func はデコレート対象の関数（ここでは multiply）
    # 【2】ラップされた関数 wrapper を定義（任意の引数を受け取り、処理の前後にログを出す）
    def wrapper(*args, **kwargs):  # *args, **kwargs により、どんな関数にも対応できるようにする
        print(f"[LOG] {func.__name__} を呼び出します")  # 【3】呼び出し前のログ出力
        result = func(*args, **kwargs)  # 【4】元の関数（multiply）を実行し、その結果を得る
        print(f"[LOG] 結果: {result}")  # 【5】呼び出し後のログ出力（結果を表示）
        return result  # 【6】元の関数の結果をそのまま返す（透過的な挙動を保つ）
    return wrapper  # 【7】ラップされた関数を戻り値として返す（デコレート完了）

# 【8】@with_logging によって、multiply 関数が自動的に wrapper に置き換えられる
@with_logging  # これは内部的に multiply = with_logging(multiply) と同じ意味
def multiply(x, y):  # 【9】引数 x と y を掛け合わせる簡単な関数
    return x * y  # 【10】計算結果を返す（もとの関数の本体）

# 【11】multiply(3, 4) を呼び出すと、実際には wrapper(3, 4) が実行される
print(multiply(3, 4))  # 【11】ログが出力され、その後結果が出力される（→ 12）
```
```: 実行結果
[LOG] multiply を呼び出します
[LOG] 結果: 12
12
```

### まとめ
| 構造                | 説明                                   | 例                                 |
| ----------------- | ------------------------------------ | --------------------------------- |
| `f()()`           | 関数を返す関数を呼び出し、さらにその関数を実行              | `greeting_factory("ja")()`        |
| `lambda: lambda:` | 無名関数で `f()()` 構造を簡潔に記述               | `lambda: (lambda: "ok")`          |
| `f(x)(y)`         | 引数を段階的に与える「カリー化」                     | `add = lambda x: lambda y: x + y` |
| `@decorator`      | 関数をラップして別の関数として返す（`f -> wrapper(f)`) | `@with_logging`                   |

# Q.17-8
**`return` を含む `try` ブロック内で `finally` が値を書き換える構造と、それが戻り値に与える影響を例とともに説明せよ。**

### 問題背景
#### 【1】`try–finally` における制御フローの特殊性
Python において `try` ブロック内で `return` が実行されても、`finally` 節はその後に必ず実行される。この特性により、次のような構造が可能になる：
```python
def func():
    try:
        return 1
    finally:
        print("finally 実行")
```
上記の `func()` を呼び出すと、`1` が返る前に `finally` の中身が実行される。

#### 【2】戻り値への影響の有無
ここで重要なのは、`finally` 節内で 戻り値を格納した変数を書き換えたとしても、それが実際の戻り値に反映されるかは「変数の型と再代入の有無」によって異なるという点である。

| 状況                                           | 結果               |
| -------------------------------------------- | ---------------- |
| `return` が数値（immutable）で、`finally` で変数を再代入   | 影響なし（戻り値は変更されない） |
| `return` がリストなどのmutable型で、`finally` で要素を書き換え | 影響あり（戻り値が変更される）  |

これは、`return` の直後に 「戻り値が一時的に保存される」→「`finally` 節が実行される」→「戻り値が返される」 というPythonの実行順序に起因する。

### 解答例とコードによる実践
ここでは以下の2例を示す：

【例1】immutableなオブジェクト（int）での再代入
【例2】mutableなオブジェクト（list）での変更

### 【A】immutable（int型）で `finally` が変数を書き換えるが戻り値には影響しない

```python
# 【1】test_immutable 関数を定義する（戻り値と finally の関係を確認するためのテスト関数）
def test_immutable():
    result = 0               # 【2】整数型の変数 result を 0 で初期化（int はイミュータブル）

    try:
        result = 1           # 【3】result に新たに 1 を代入（元の 0 は破棄される）
        return result        # 【4】ここで return される値は「いったん内部に保持」される（まだ返されていない）

    finally:
        result = 999         # 【5】finally 節で result を変更（ただし return の値には影響しない）
        print("finallyでresultを変更")  # 【6】ログを出力（finally 節は return の前に必ず実行される）

# 【7】test_immutable() を呼び出して、戻り値を表示する
print("戻り値（immutable）:", test_immutable())  # 【8】return で指定された「1」が最終的に返される
```
```: 実行結果
finallyでresultを変更
戻り値（immutable）: 1
```

### 【B】mutable（list型）で `finally` が内容を変更すると戻り値にも影響する

```python
def test_mutable():
    result = [1, 2, 3]              # 【1】リスト（mutable）型の変数を定義
    try:
        return result              # 【2】resultリストが戻り値として一時保存される
    finally:
        result[0] = 999            # 【3】resultの中身を書き換える（インプレース）
        print("finallyでリストを変更")  # 【4】

print("戻り値（mutable）:", test_mutable())  # 【5】
```
```: 実行結果
finallyでリストを変更
戻り値（mutable）: [999, 2, 3]
```

### 本問のまとめ
・Pythonでは、`try` ブロック内の `return` 実行後であっても、`finally` 節は必ず実行されるという構造的特性を持つ。
・`return` に使用された変数が immutable（int, strなど） であり、`finally` 内で「変数を再代入」しても、戻り値は変化しない。
・一方、戻り値が mutable（list, dictなど） なオブジェクトであり、`finally` 内で その内容を書き換えると、実際に返されるオブジェクトにも影響する。
・このため、関数設計において 戻り値を `try` 内で確定させたつもりでも、`finally` の副作用により期待と異なる値が返る可能性がある。
・特に `finally` 内で 戻り値オブジェクトを「変更」するコードが紛れていると、非常に発見しづらいバグの温床となる。

## 【補足】
**`finally` 内で `return` を使うと、`try` 内の `return` はどのように上書きされるか。その構造と挙動を例とともに説明せよ。**

### 問題背景
#### 【1】基本仕様：`finally` は必ず実行される
Pythonの `try–finally` において、`try` 内で `return` が発動されたとしても、その値が「即座に」返るわけではない。
Pythonインタプリタは一度、戻り値を記憶した状態で `finally` を実行し終えるのを待つ。

```python
def func():
    try:
        return "A"
    finally:
        return "B"
```
このようなコードでは、`"A"` を返すように見えて、実際には `"B"` が返る。
これは `finally` 節の中で `return` が書かれていると、それが最終的な戻り値として採用される（上書きされる）ためである。

#### 【2】制御フロー上の優先度
| 制御文の場所               | 優先度（実際に使われるか） |
| -------------------- | ------------- |
| `try` の `return`     | 下位            |
| `finally` の `return` | 上位（強制的に上書き）   |

この仕様は、JavaやC#とは異なる挙動であり、Pythonに特有な注意点である。

#### 【3】設計的なリスク
・`try` 内の `return` に期待してロジックを書くと、`finally` の `return` によってすり替えられる
・例外の再送出やエラー情報の喪失が起きやすい
・可読性が著しく低下するため、実務では基本的に `finally` に `return` を書くべきでない

### 解答例とコードによる実践
```python
# 【1】関数を定義する。try の return と finally の return の挙動を比較するためのデモ関数
def return_override_demo():
    try:
        print("tryブロック内: 処理開始")        # 【2】まず try 節の処理が始まり、ログを出力する
        return "値A（tryのreturn）"             # 【3】ここで return が発生するが、すぐには返らず、finally が優先される
    finally:
        print("finallyブロック内: 処理開始")     # 【4】return の前に必ず実行される finally 節が開始
        return "値B（finallyのreturn）"         # 【5】ここで再度 return され、try の return が完全に上書きされる

# 【6】関数を呼び出し、その戻り値を result に格納する
result = return_override_demo()  # 【6】関数内で return は2回発生するが、実際に返るのは「finally 側」の return

# 【7】実際に返ってきた値（result）を表示する
print("実際の戻り値:", result)   # 【7】出力されるのは "値B（finallyのreturn）"
```
```: 実行結果
tryブロック内: 処理開始
finallyブロック内: 処理開始
実際の戻り値: 値B（finallyのreturn）
```

### 補充：例外送出も同様に上書きされる
次の例では try 内で例外を発生させているが、finally の return によって例外が無視される：

```python
# 【1】関数を定義する。try で例外を発生させ、finally で return することで例外が握りつぶされるかを確認する
def exception_swallowed():
    try:
        raise ValueError("例外A")       # 【2】明示的に ValueError を発生させる（通常ならここで関数は異常終了）
    finally:
        return "値B（例外が飲み込まれる）"  # 【3】finally に return があると、例外は握りつぶされて戻り値としてこちらが優先される

# 【4】関数を呼び出し、戻り値を出力する
print(exception_swallowed())  # 【5】例外は報告されず、"値B（例外が飲み込まれる）" が表示される
```
```: 実行結果
値B（例外が飲み込まれる）
```
`raise` が行われたにもかかわらず、`finally` の `return` によって「例外が発生しなかった」ことになる。
これは 例外のサプレッション（抑制）と呼ばれる動作であり、非常に危険であるため実務では避けるべきである。

### まとめ
・`finally` 節に `return` を記述すると、`try` 節での `return` は上書きされ、無視される。
・この「`return`の上書き現象」により、戻り値の意図と実際が乖離しやすく、デバッグが困難になる。
・同様に `raise` による例外送出も `finally` の `return` によって握り潰されてしまう。
・よって、`finally` 節での `return` は極力避けるべきであり、リソース解放やログ記録などの「副作用だけを記述する」ことが望ましい。

# あとがき
今回は「ユーザー定義関数における戻り値」について扱いました。個人的にはもっと扱いたかった問題も多いのですが、盲点となりがちな話や重要そうに感じる話題を中心に選んでみました。次回は、「関数の設計と記述スタイル」について扱う予定です。

# 参考文献
[1] 独習Python (2020, 山田祥寛, 翔泳社)
[2] Pythonクイックリファレンス 第4版（2024, Alex, O’Reilly Japan）
[3] 【Python 猛特訓】100本ノックで基礎力を向上させよう！プログラミング初心者向けの厳選100問を出題！(Youtube, https://youtu.be/v5lpFzSwKbc?si=PEtaPNdD1TNHhnAG)