【Python】Noneの扱い方でハマらないために

Python

こんにちは、かつコーチです。

AttributeError: 'NoneType' object has no attribute '◯◯'」。
Pythonを書いていると、このエラーに一度は出会うのではないでしょうか。

私自身、初心者の頃にこのエラーの意味が分からず、何時間も原因を探し回った経験があります。
この記事では、Noneとは何か、正しい比較方法、そしてこのエラーへの対処法を丁寧に解説します。

Noneとは?

「何もない」ことを表す特別な値

Noneとは、Pythonにおいて「値が存在しない」「何もない」ことを表す特別な値です。

他の言語でのnullnilに相当します。
0""(空文字)とは違い、Noneは「そもそも値がセットされていない」状態を表します。

result = None
print(result)  # None
print(type(result))  # <class 'NoneType'>

NoneはNoneTypeという専用の型を持っています。

なぜNoneに遭遇するのか

Noneは意図せず現れることがよくあります。
代表的なのは、戻り値を明示的に指定していない関数を呼んだときです。

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


result = greet("かつコーチ")
print(result)  # None

greet()関数はprint()しているだけでreturn文がありません。
Pythonではreturnを書かない関数は、自動的にNoneを返す仕様になっています。

また、辞書から存在しないキーを.get()で取得したときも、Noneが返ってきます。

data = {"name": "かつコーチ"}
age = data.get("age")
print(age)  # None

基本の書き方

手順1:Noneかどうかを判定する

Noneかどうかを判定するときは、==ではなくisを使うのが正しい書き方です。

value = None

if value is None:
    print("値がありません")

否定形の場合はis not Noneを使います。

value = "データあり"

if value is not None:
    print(f"値: {value}")

手順2:なぜ==ではなくisを使うのか

多くの初心者は、Noneの比較に==を使ってしまいがちです。

value = None

if value == None:  # 動くが推奨されない書き方
    print("値がありません")

このコードは一見正しく動きますが、Pythonの公式スタイルガイド(PEP 8)ではis Noneの使用が推奨されています。

理由は2つあります。

  1. ==は演算子として上書き(オーバーライド)できてしまうため、独自クラスで__eq__メソッドが定義されていると、意図しない比較結果になる可能性がある
  2. isは「同一のオブジェクトかどうか」を比較する演算子で、Noneはプログラム内で常に単一のオブジェクトとして存在するため、isの方が意味的に正確
value = None

# 良い書き方
if value is None:
    print("値がありません")

isは「同じものを指しているか」、==は「値が等しいか」を調べる演算子だと覚えておくと区別しやすくなります。
Noneのような「特別な単一の値」を判定する場合はisを使う、というルールを徹底しましょう。

手順3:Noneをデフォルト値として活用する

関数の引数のデフォルト値として、Noneを使うテクニックもよく使われます。

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items


print(add_item("りんご"))  # ['りんご']
print(add_item("みかん"))  # ['みかん'](前回の結果が引き継がれない)

ミュータブル(変更可能)なリストや辞書を直接デフォルト値にすると、呼び出しをまたいで値が共有されてしまうという落とし穴があります。
items=Noneとしておき、関数内でif items is None: items = []と初期化するのが安全な書き方です。

つまずきやすい設定・注意点

NoneはFalsyな値(if文で False と評価される値)でもあります。
そのため、if not value:という書き方をすると、Noneだけでなく0や空文字""、空リスト[]も同じ条件でTrueになってしまいます。

value = 0

if not value:
    print("値がありません")  # 0でもこの分岐に入ってしまう

「Noneかどうか」だけを厳密に判定したい場合は、not valueではなく必ずvalue is Noneを使いましょう。

よくあるつまずきポイント・エラー対処

AttributeError: ‘NoneType’ object has no attribute

Before(つまずいたコード)

def find_user(user_id, users):
    for user in users:
        if user["id"] == user_id:
            return user
    # 見つからない場合は何も return しない


users = [{"id": 1, "name": "かつコーチ"}]
user = find_user(2, users)
print(user["name"])

実行すると、次のエラーが出ます。

Traceback (most recent call last):
  File "main.py", line 10, in <module>
    print(user["name"])
TypeError: 'NoneType' object is not subscriptable

私はこのエラーに、業務でユーザー検索処理を書いていたときに出会いました。
find_user()が対象のユーザーを見つけられなかった場合、return文が実行されずに関数の最後まで到達し、暗黙的にNoneが返っていたのです。

そのNoneに対してuser["name"]のように辞書アクセスをしようとしたため、エラーになりました。
(辞書アクセスの場合はTypeErroruser.nameのような属性アクセスの場合はAttributeError: 'NoneType' object has no attribute 'name'になります。)

After(改善したコード)

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


users = [{"id": 1, "name": "かつコーチ"}]
user = find_user(2, users)

if user is None:
    print("ユーザーが見つかりませんでした")
else:
    print(user["name"])

関数の最後に明示的にreturn Noneを書くことで、「見つからない場合はNoneを返す」という意図がコードから明確に読み取れるようになります。
さらに、呼び出し側でもif user is None:のチェックを入れることで、Noneに対する不正なアクセスを未然に防げます。

get()の戻り値がNoneであることを見落とす

Before(つまずいたコード)

config = {"host": "localhost"}

port = config.get("port")
url = f"{config.get('host')}:{port + 1}"
print(url)

実行すると、次のエラーが出ます。

Traceback (most recent call last):
  File "main.py", line 4, in <module>
    url = f"{config.get('host')}:{port + 1}"
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

config"port"キーが存在しないため、config.get("port")はNoneを返します。
そのNoneに対して+ 1という数値演算をしようとして、エラーになりました。

After(改善したコード)

config = {"host": "localhost"}

port = config.get("port", 8080)
url = f"{config.get('host')}:{port + 1}"
print(url)

.get()の第2引数にデフォルト値を指定しておくことで、キーが存在しない場合でもNoneではなく指定した値(この例では8080)が返るようになります。
「Noneかもしれない値」を後から演算に使う可能性がある場合は、.get()の段階でデフォルト値を渡しておくと安全です。

まとめ

この記事のポイント

  • Noneは「値が存在しない」ことを表す特別な値で、NoneTypeという専用の型を持ちます
  • Noneの比較には==ではなくis Noneis not Noneを使いましょう
  • not valueはNone以外(0や空文字など)も同じ条件になるため、Noneの厳密な判定には向きません
  • 関数が値を返さない可能性がある場合は、明示的にreturn Noneを書いて意図を明確にしましょう
  • .get()にデフォルト値を渡しておくと、Noneに起因するエラーを未然に防げます

次に読むべき記事

例外処理の基本をまだ押さえていない方は、try-exceptで例外処理を書く基本の記事もあわせてご覧ください。
他のエラーメッセージの読み方が気になる方は、よくあるPythonエラーの読み方の記事も参考になります。

タグ: Python, 初心者向け, エラー解決

タイトルとURLをコピーしました