スポンサーリンク

Python初心者が知らない「戻り値設計」完全ガイド|None・例外・型ヒントで壊れない関数を書く

クラス設計・OOP入門

Pythonで関数を書けるようになってくると、ある日ふとこんな違和感を覚えませんか?

「この関数、成功したらリストが返ってくるけど、失敗したらFalse…?」
「呼び出すたびにif文とNoneチェックが増えていくんだけど……」

実はこれ、文法の問題ではなく「戻り値の設計」の問題なんです。

Pythonの関数はとても自由です。数値も文字列も、リストも辞書も、場合によっては関数そのものだって返せます。 でも、その自由さゆえに、設計を意識しない戻り値は、あとからコードを一気に読みにくく、壊れやすくしてしまいます。

特に初心者のうちは、

  • 正常時と異常時で戻り値の型が変わる
  • FalseやNoneが混在する
  • 呼び出し側が「察する」コードになる

といった設計を、悪気なく書いてしまいがちです。 そしてそのツケは、少し規模が大きくなった瞬間にやってきます 😅

この記事では、そんな状態から一歩抜け出すために、

  • なぜ戻り値設計が重要なのか
  • やってはいけない典型的なNG例
  • None・例外・複数戻り値の正しい使い分け
  • 「壊れにくく、読みやすい関数」を作る考え方

を、初心者にも分かる言葉で、でも実務でも通用するレベルまで掘り下げて解説していきます。

「なんとなく動くコード」から、
「意図が伝わるコード」へ。

そんな一段レベルアップするためのガイドとして、ぜひ最後まで読んでみてください ✨


なぜ「戻り値設計」が重要なのか

関数はただの処理のかたまりではありません。 「引数を受け取り、結果を返す」という約束(契約)を持った部品です。

この契約が曖昧だと、関数を使う側は常に不安になります。

たとえば、次のような関数があったとします。


def find_user(user_id):
    if user_id == 1:
        return {"id": 1, "name": "Alice"}
    else:
        return False

一見すると動きそうですが、使う側はこう考えなければなりません。

  • 戻り値は辞書?それともFalse?
  • ifで毎回チェックが必要?
  • チェックを忘れたらどうなる?

この「考えなければならないこと」が増えるほど、コードは読みにくく、壊れやすくなります。

Pythonは動的型付け言語なので、こうした問題は実行するまで気づけないことも多いです。 その結果、

  • ある条件でだけクラッシュする
  • NoneTypeエラーが突然出る
  • 修正のたびに別の場所が壊れる

といった「よく分からないバグ」に悩まされがちになります。

特に初心者のうちは、

  • 「とりあえず動けばOK」
  • 「失敗したらFalse返せばいいか」

と考えがちですが、これは後から必ず自分を苦しめます 😌

戻り値設計を意識するというのは、

  • 関数が何を返すのかを明確にする
  • 使う側が迷わないようにする
  • エラーを早く・安全に見つける

ということです。

これは難しいテクニックではありません。 考え方を少し変えるだけで、コードの読みやすさと安心感は驚くほど変わります。

次の章では、その土台となる return文の基本原則 から整理していきましょう。




return文の基本原則を整理しよう

戻り値設計の話に入る前に、まずは return文そのものの性質 をしっかり押さえておきましょう。 ここを曖昧にしたままだと、設計の話がふわっとしてしまいます。

returnが実行された時点で関数は終了する

Pythonでは、return が実行された瞬間に関数の処理は終了します。


def sample():
    print("start")
    return 10
    print("end")

この関数を呼び出すと、出力はこうなります。


start

return の後ろに書いたコードは実行されません。 このようなコードは「デッドコード」と呼ばれ、可読性を下げる原因になります。

returnを書かないとNoneが返る

初心者が意外と見落としがちなのが、このルールです。


def do_nothing():
    pass

この関数は何も返していないように見えますが、実際には None を返しています。

また、値を書かずに return だけを書いた場合も同じです。


def do_nothing():
    return

Pythonでは「何も返さない関数」=「Noneを返す関数」だと覚えておきましょう。

Pythonの関数は何でも返せる

Pythonの関数は非常に柔軟で、さまざまなオブジェクトを戻り値にできます。

  • 数値・文字列
  • リスト・辞書・タプル
  • クラスのインスタンス
  • 関数そのもの

この自由さは強力ですが、裏を返すと 「制約がない」 ということでもあります。

だからこそ、

  • どんな型を返すのか
  • 失敗したときはどうするのか

を決めずに書くと、設計が一気に崩れます。

次の章からは、いよいよ本題です。 「実務で壊れにくい戻り値設計」を、具体例と一緒に見ていきましょう。




戻り値設計の基本パターン

戻り値の型は必ず統一する

戻り値設計でもっとも重要なのが、「関数が返す型を常に一定にする」ことです。

初心者がやりがちなNG例を見てみましょう。


def search_items(keyword):
    items = ["apple", "banana", "orange"]
    result = [item for item in items if keyword in item]

    if result:
        return result
    else:
        return False

この関数は、一見すると親切そうですが、使う側はかなり困ります。


items = search_items("ap")

for item in items:
    print(item)

もし False が返ってきたら、ここでエラーになります。 そのため、呼び出し側は毎回こう書かざるを得ません。


items = search_items("ap")

if items is not False:
    for item in items:
        print(item)

こうしてコードは少しずつ、

  • 条件分岐が増える
  • 意図が読み取りにくくなる
  • 修正が怖くなる

という状態に近づいていきます 😅

改善パターン①:Noneを返す

「見つからなかった」という状態を None で表現するのは、Pythonでは一般的な設計です。


def search_items(keyword):
    items = ["apple", "banana", "orange"]
    result = [item for item in items if keyword in item]

    if result:
        return result
    return None

これで、戻り値の意味がはっきりします。

  • list → 結果あり
  • None → 結果なし

改善パターン②:空のコレクションを返す

リストや辞書を返す関数では、空のオブジェクトを返す設計が特におすすめです。


def search_items(keyword):
    items = ["apple", "banana", "orange"]
    return [item for item in items if keyword in item]

この場合、見つからなければ空のリスト [] が返ります。

呼び出し側は何も考えずに書けます。


for item in search_items("ap"):
    print(item)

このシンプルさが、後々の保守性に大きく効いてきます。

戻り値の型を統一するという考え方は、 「たまたま動くコード」から「信頼できるコード」へ変わる最初の一歩です。

この設計思想を、より体系的に学びたい人には次の一冊がとても役立ちます。

Effective Python 第2版
関数設計・戻り値・例外の考え方を、実務視点で深く理解できる定番書です。

Amazonでチェックする
楽天でチェックする

次は、複数の値を安全に返す方法について見ていきましょう。




複数の値を安全に返す

関数の中で「結果」と「付加情報」を一緒に返したくなる場面はよくあります。

たとえば、

  • 処理結果とステータス
  • 計算結果とエラーメッセージ
  • 取得データと件数

といったケースです。

Pythonではタプルでまとめて返せる

Pythonでは、カンマ区切りで複数の値を返すと、自動的にタプルとして扱われます。


def divide(a, b):
    if b == 0:
        return None, "0で割ることはできません"
    return a / b, None

呼び出し側では、アンパックして受け取ります。


result, error = divide(10, 2)

if error is None:
    print(result)
else:
    print(error)

このように、戻り値の意味が明確になり、 「何が返ってくるか分からない」状態を避けられます。

タプルの弱点

ただし、タプルには弱点もあります。

  • 要素が増えると意味が分かりにくい
  • 順番を間違えてもエラーにならない

次のコード、少し怖くないですか?


value, message, status, retry = some_function()

どれが何なのか、呼び出し側だけでは判断しづらくなってきます。

namedtupleで可読性を上げる

この問題を解決する方法のひとつが namedtuple です。


from collections import namedtuple

Result = namedtuple("Result", ["value", "error"])

def divide(a, b):
    if b == 0:
        return Result(value=None, error="0で割ることはできません")
    return Result(value=a / b, error=None)

呼び出し側は、ドット記法で値にアクセスできます。


result = divide(10, 2)

if result.error is None:
    print(result.value)
else:
    print(result.error)

これだけで、コードの読みやすさはかなり変わります。

「何番目の値だったっけ?」と悩む時間が減り、 関数の意図が自然に伝わるようになります。

次は、異常系(エラー)をどう設計するかについて解説します。




異常系(エラー)はどう設計するべきか

戻り値設計で多くの人が悩むのが、 「失敗したとき、戻り値で表現するか?例外を投げるか?」という問題です。

ここを曖昧にすると、

  • FalseやNoneが大量発生する
  • if文だらけのコードになる
  • エラーを見逃す

といった状態に陥ります。

Pythonでは「例外」が基本

Pythonでは、回復不能なエラーや契約違反は例外で表現するのが基本です。

たとえば、

  • 引数の型が想定と違う
  • 本来あり得ない状態に入った

こういったケースを戻り値で表現すると、 呼び出し側がチェックし忘れた瞬間にバグになります。


def get_user_age(user):
    if "age" not in user:
        raise ValueError("ageが存在しません")
    return user["age"]

例外を使えば、

  • 問題が起きた瞬間に止まる
  • 原因が明確になる
  • バグを早期に発見できる

というメリットがあります。

回復可能なエラーは戻り値で表現する

一方で、すべてを例外にすれば良いわけではありません。

たとえば、

  • 検索結果が0件
  • ユーザー入力が空だった

といった「よくある状態」は、異常ではなく想定内です。

この場合は、

  • Noneを返す
  • 空のリストを返す

といった戻り値設計の方が自然です。

戻り値と例外の役割を分ける

シンプルな判断基準としては、次のように考えると迷いにくくなります。

  • 呼び出し側が対処できる → 戻り値
  • 設計ミス・契約違反 → 例外

この線引きを意識するだけで、 関数の責務が一気にクリアになります。

こうした考え方は、実は多くの良書で共通しています。

Clean Code アジャイルソフトウェア達人の技
「関数の責務」「例外の扱い」「一貫した設計」という観点から、 戻り値設計を根本から見直すヒントが詰まった定番書です。

Amazonでチェックする
楽天でチェックする

次は、戻り値設計をさらに強化するための 実践的なテクニックをまとめて紹介します。




戻り値設計を強化する実践テクニック

ここまでで、戻り値設計の「考え方」はかなり整理できたと思います。 最後に、日常的なコーディングで効いてくる実践テクニックをまとめておきます。

型ヒントで「戻り値の約束」を明文化する

戻り値設計を一段レベルアップさせてくれるのが、型ヒントです。


def get_user_name(user_id: int) -> str | None:
    if user_id == 1:
        return "Alice"
    return None

これだけで、

  • この関数は strNone を返す
  • Falseや数値は返らない

という契約が一目で分かります。

エディタや静的解析ツールを使っていると、 呼び出し側でのミスも早い段階で検出できるようになります。

複雑な式は一時変数に置く

次のような return、書いたことありませんか?


return a * b / c if c != 0 else None

短くて一見スマートですが、 後から読むと「何をしている関数なのか」が分かりづらくなります。

一度、意味のある名前を付けてから返すだけで印象は変わります。


if c == 0:
    return None

result = a * b / c
return result

デバッグもしやすくなり、意図も伝わりやすくなります。

副作用に頼らず、値を返す

戻り値設計が崩れやすいパターンのひとつが、副作用です。


total = 0

def add(value):
    global total
    total += value

この関数は None を返しますが、 実際には外部の状態を書き換えています。

こうした関数は、使う側からすると挙動が分かりにくくなります。

可能な限り、


def add(total, value):
    return total + value

のように、計算結果を戻り値として返す設計を意識しましょう。

ツールの力を借りる

人間の注意力には限界があります。

そのため、

  • リンター
  • フォーマッター
  • 静的解析ツール

を使って、戻り値の揺れや設計ミスを早めに検知するのがおすすめです。

コードの質は、 才能よりも仕組みで安定させる方がずっと確実です。




まとめ

今回は、Python初心者が意外と見落としがちな 「戻り値設計」について、基礎から実践まで整理してきました。

ポイントを振り返ると、次の通りです。

  • 戻り値は「関数と呼び出し側の契約」である
  • 正常時と異常時で戻り値の型を変えない
  • コレクションは空で返す設計が強い
  • 複数の値はタプルやnamedtupleで意味を明確にする
  • 契約違反や回復不能な状態は例外で表現する
  • 型ヒントを使うと設計がコードとして残る

戻り値設計は、最初のうちは地味に感じるかもしれません。 でも実際には、

  • if文が減る
  • エラーの原因が分かりやすくなる
  • 安心してコードを変更できる

といった形で、あとからじわじわ効いてきます。

私自身も、Pythonを書き始めた頃は 「とりあえずFalse返しとけばいいかな?」という設計をよくしていました 😅 でも、ある程度コード量が増えたところで、一気に破綻しました。

それ以来、戻り値は

  • 何を返す関数なのか
  • 失敗とは何か
  • 呼び出し側はどう書けるか

を先に考えてから書くようにしています。

その結果、コードを読む時間も、直す時間も、かなり減りました。

戻り値設計はテクニックというより、考え方です。 ぜひ、次に関数を書くときから、少しだけ意識してみてください。

きっと、「Pythonが前より書きやすくなった」と感じられるはずです ✨


あわせて読みたい

戻り値設計の考え方は、例外設計・型・設計全般と強くつながっています。 理解を一段深めたい方は、次の記事もあわせて読んでみてください。

これらの記事を読むことで、 「戻り値」だけでなく 設計として一貫したPythonコード が書けるようになります。


参考文献


よくある質問(Q&A)

Q
戻り値にNoneを使うのは危険ではありませんか?
A

結論から言うと、正しく設計されていれば危険ではありません

Noneが問題になるのは、「Noneが返る可能性があること」がコードから読み取れない場合です。 型ヒントや関数名、ドキュメントで意図が明確になっていれば、Noneはとても分かりやすい表現になります。

特に「見つからなかった」「存在しなかった」という状態は、FalseよりもNoneの方が意味が明確です。

また、リストや辞書を返す関数であれば、Noneではなく空のコレクションを返す設計にすることで、 呼び出し側のNoneチェック自体を不要にできます。

Q
Falseを戻り値として使う設計は全部ダメですか?
A

すべてがダメというわけではありませんが、初心者にはおすすめしません

Falseは 0 や空のコレクションと同じ「偽」として扱われるため、 意図しない条件分岐に入りやすいという欠点があります。

「成功・失敗」を表したい場合でも、Noneや例外、あるいは明示的なResultオブジェクトの方が、 コードの意味がはっきりします。

どうしてもTrue / Falseを返したい場合は、 「判定だけを行う関数」であることが明確なケースに限定すると安全です。

Q
Result型のような設計は初心者でも使うべきですか?
A

最初から無理に使う必要はありません。

Result型は、例外を使わずにエラーを安全に伝えたい場合にとても強力ですが、 考え方としては少し抽象度が高めです。

まずは、

  • 戻り値の型を統一する
  • Noneと例外を正しく使い分ける

この2点をしっかり身につけるだけで、コードの品質は大きく向上します。

その上で、「例外を使わない設計が欲しくなった」と感じたタイミングで、 Result型の考え方を取り入れるのがおすすめです。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク