コードを動かしたら、AttributeError: 'NoneType' object has no attribute 'append' という表示が出て止まってしまった。そんな経験はありませんか?
自分ではリストを扱っているつもりなのに、Pythonは NoneType だと言ってくる。初心者のころは、何を言われているのかさっぱりわからないエラーのひとつだと思います。
でも安心してください。AttributeErrorは、メッセージの読み方さえ覚えれば原因をかなり絞り込めるエラーです。この記事では、仕組みからよくある原因、意外な落とし穴までを順番に見ていきます。
サンプルの実行結果は、手元のPython 3.11、3.12、3.13で確認したものです。バージョンによって表示が違う部分は、その都度お伝えします。
AttributeErrorとはどんなエラーなのか¶
まずは、そもそも属性とは何かを押さえておきましょう。Pythonでは、オブジェクトが持っているデータやメソッドのことをまとめて属性と呼びます。
たとえば文字列の upper() やリストの append() は、それぞれのオブジェクトが持っているメソッドです。ドットのあとに名前を書いて呼び出すものは、すべて属性だと考えて大丈夫です。
AttributeErrorは、このオブジェクトが持っていない属性を使おうとしたときに出るエラーです。公式ドキュメントでも、属性の参照や代入が失敗したときに送出されると説明されています。
前回の記事で紹介したNameErrorと似ていますが、探している場所が違います。NameErrorは名前そのものが見つからないとき、AttributeErrorはドットの右側が見つからないときに出ます。 【関連記事】PythonのNameErrorとは?name 'x' is not definedの原因と直し方
エラーメッセージの読み方¶
次に、メッセージのどこを見ればよいのかを確認しましょう。文字列の upper() を打ち間違えたコードを例にします。
name = "python"
print(name.uper())
実行すると、最後の行に次のようなメッセージが出ます。
AttributeError: 'str' object has no attribute 'uper'. Did you mean: 'upper'?
このメッセージは3つの部分に分けて読むと理解しやすくなります。それぞれの意味を表にまとめました。
| 部分 | 意味 | この例では |
|---|---|---|
| 'str' object | どの型のオブジェクトで起きたか | 文字列 |
| has no attribute 'uper' | 見つからなかった属性の名前 | uper |
| Did you mean: 'upper'? | Pythonが推測した正しい名前 | upper |
いちばん大事なのは、最初の型の部分です。ここに自分の想定と違う型が書かれていたら、原因は打ち間違いではなく、変数の中身そのものにあります。
Did you mean の提案は、Python 3.10から加わった機能です。どの行で止まったのかは、メッセージの少し上にあるtracebackで確認できます。 【関連記事】Pythonのエラー文はどこを読めばいい?初心者向けtracebackの見方
AttributeErrorのよくある原因¶
AttributeErrorの原因は、大きくいくつかのパターンに分けられます。エラーが出たら、まずこの表の中から当てはまるものを探してみてください。
| 原因 | 例 | メッセージの型 |
|---|---|---|
| メソッド名の打ち間違い | name.uper() | 想定どおりの型 |
| 中身がNoneになっている | sort()の戻り値を使う | NoneType |
| 型を取り違えている | 文字列に append() | str など想定外の型 |
| 辞書をドットで読もうとした | user.name | dict |
| 自分のファイル名がモジュール名とかぶった | random.py を作った | module |
| 存在しない関数をモジュールから呼んだ | math.squareroot | module |
この中でも、とくに初心者がつまずきやすいものを、ここから順番にくわしく見ていきます。
'NoneType' object has no attribute が出る理由¶
AttributeErrorの中で、いちばんよく見かけるのがこのパターンだと思います。メッセージの型が NoneType になっているときは、変数の中身が None になっています。
よくあるのが、リストの sort() の戻り値を変数に入れてしまうケースです。sort() はリストをその場で並べ替えるメソッドで、戻り値は None なのです。
nums = [3, 1, 2]
result = nums.sort() # sort()はNoneを返す
try:
result.append(4)
except AttributeError as e:
print(e) # 'NoneType' object has no attribute 'append'
print(e.name) # append
print(e.obj) # None
このコードを動かすと、コメントに書いたとおりの結果が表示されます。並べ替えた新しいリストがほしいときは、sorted(nums) を使うのが正解です。
ほかにも、正規表現の re.match() が一致しなかったときや、辞書の get() でキーが見つからなかったときにも None が返ります。戻り値を確かめずにドットでつなぐと、同じエラーに出会います。
Noneそのものの性質については、こちらの記事でくわしく解説しています。 【関連記事】PythonのNoneとは?使い方と判定方法を初心者向けに解説
型を取り違えている¶
次に多いのが、変数の型を思い込みで扱ってしまうケースです。リストだと思っていた変数が、実は文字列だったということはよくあります。
たとえば、input() で受け取った値はいつも文字列です。数字を入力しても、そのままでは数値のメソッドやリストのメソッドは使えません。
辞書をドットで読もうとするミスも定番です。user["name"] と書くべきところを user.name と書くと、'dict' object has no attribute 'name' と表示されます。
JavaScriptを学んだことがある人は、とくにこの間違いをしがちです。JavaScriptのオブジェクトはドットで中身を読めるので、手がそちらを覚えているんですね。
型がわからないときは、エラーの出た行の直前で print(type(変数)) を書いてみましょう。想定と違う型が表示されたら、その変数に値を入れている場所をさかのぼって調べます。
ファイル名がモジュールとかぶる落とし穴¶
ここからは、初心者が驚きやすい落とし穴を紹介します。サイコロのプログラムを作ろうとして、自分のファイルを random.py という名前で保存したとしましょう。
import random
print(random.randint(1, 6))
コードは正しいのに、実行すると AttributeError が出てしまいます。原因は、Pythonが標準ライブラリの random ではなく、自分で作った random.py を読み込んでしまうことです。
Pythonはモジュールを探すとき、実行したファイルのあるフォルダを先に調べます。そのため、標準ライブラリと同じ名前のファイルを作ると、本物のモジュールが隠れてしまうのです。
Python 3.13からはヒントが出る¶
うれしいことに、Python 3.13からはこの状況でヒントが表示されるようになりました。手元の3.13で試すと、メッセージの最後に次の一文が付きました。
(consider renaming '.../random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)
これは、標準ライブラリと同じ名前なので、ファイル名の変更を検討してくださいという意味です。公式のWhat's Newでも、numpy.py のような外部ライブラリと同じ名前の場合にも案内が出ると紹介されています。
一方、Python 3.12で同じファイルを動かすと、most likely due to a circular import という表示になりました。循環importが原因だと案内されるので、初心者は余計に混乱してしまいます。
直し方は、自分のファイル名を dice.py のような別の名前に変えることです。同じフォルダに pycache が残っている場合は、それも削除しておくと安心です。
循環importそのものについては、こちらの記事で解説しています。 【関連記事】Pythonの循環importとは?エラーの原因と直し方
実務でAttributeErrorに泣かされた話¶
ここで、私自身の失敗談をひとつ紹介させてください。私は10年ほどエンジニアとして開発に関わってきましたが、本番環境で 'NoneType' object has no attribute を出してしまったことがあります。
原因は、データベースからユーザーを検索する関数でした。見つからなかったときに None を返す仕様だったのに、呼び出し側では必ず見つかる前提で user.email と書いていたのです。
テストでは存在するユーザーしか使っていなかったので、誰も気づきませんでした。退会したユーザーのデータが来た瞬間に、エラーが出て処理が止まってしまったのです。
それ以来、None を返すかもしれない関数を呼んだら、すぐ下で if user is None を書くようにしています。型ヒントに Optional を付けておくと、mypy のような型チェッカーが確認漏れを教えてくれるのでおすすめです。
getattrとhasattrで安全に扱う¶
続いて、属性があるかどうかわからないときの書き方を見ていきましょう。Pythonには、そのための組み込み関数が用意されています。
getattr() は、属性を名前で取り出す関数です。3つ目の引数に初期値を渡しておくと、属性がなかったときにエラーにならず、その値が返ります。
class User:
def __init__(self):
self.email = "a@example.com"
user = User()
print(getattr(user, "age", 0)) # 0
print(hasattr(user, "age")) # False
hasattr() は、属性を持っているかどうかを True か False で教えてくれます。どちらも、外から受け取ったオブジェクトを扱う場面で役立ちます。
ただし、getattrの初期値で打ち間違いまで隠してしまわないように注意してください。何でも初期値でごまかすと、本当のバグに気づけなくなります。
getattr と setattr の使い方は、こちらの記事でくわしくまとめています。 【関連記事】Pythonのgetattrとsetattrとは?属性を名前で操作する方法
try・exceptで受け止めるときの注意点¶
AttributeErrorも例外の一種なので、try と except で受け止められます。Python 3.10以降では、e.name で見つからなかった属性名、e.obj で対象のオブジェクトを取り出せます。
とはいえ、AttributeErrorを try で握りつぶすのは、ほとんどの場合おすすめしません。原因の多くは、打ち間違いや None の確認漏れといったコードの書き方にあるからです。
try の範囲を広く取りすぎるのも危険です。関数の中のずっと奥で起きたAttributeErrorまで拾ってしまい、まったく別のバグを見逃すことがあります。
使うとしても、エラーになりうる1行だけを囲みましょう。エラーを隠すのではなく、なぜその型になったのかを調べるのが、いちばんの近道です。
AttributeErrorを未然に防ぐ習慣¶
最後に、AttributeErrorを減らすための習慣を紹介します。どれも今日から始められるものばかりです。
ひとつ目は、エディタの補完機能を使うことです。ドットを打ったときに候補が出てくるので、メソッド名の打ち間違いがぐっと減ります。
ふたつ目は、メソッドの戻り値を確かめるくせをつけることです。sort() や append() のように、元のデータを書き換えるメソッドは None を返すものが多いと覚えておきましょう。
みっつ目は、None を返すかもしれない値をすぐに確認することです。if 変数 is None のチェックを1行入れるだけで、多くの事故を防げます。
そして、ファイル名には標準ライブラリと同じ名前を使わないようにしましょう。random、math、json、csv、test などは、初心者がうっかり付けてしまいがちな名前です。
まとめ¶
今回は、PythonのAttributeErrorについて解説しました。最後に要点を振り返っておきましょう。
AttributeErrorは、オブジェクトが持っていない属性を使おうとしたときに出るエラーです。メッセージに書かれた型を見るだけで、打ち間違いなのか、中身が想定と違うのかを見分けられます。
とくに NoneType と表示されたら、どこかで None が紛れ込んでいる合図です。module と表示されたら、自分のファイル名がモジュールとかぶっていないかも疑ってみてください。
エラーメッセージは、あなたを責めるものではなく、どこを見ればよいかを教えてくれる道しるべです。落ち着いて型の部分を読む習慣をつければ、きっとすぐに直せるようになりますよ。
ここまでお読みいただきありがとうございました。