Python で「'NoneType' object has no attribute」が解消できない
AttributeError の対象が NoneType の場合、メソッドの戻り値や辞書の get、ライブラリ API が想定外に None を返している。
値が None である可能性を設計で潰すか、is None で早期判定してから属性アクセスする。
#python#attributeerror#nonetype#exception#type-hint
要約
'NoneType' object has no attribute 'xxx' は、None に対して属性アクセスした ことを示す。
直前のメソッド呼び出しや関数の戻り値が想定と違って None になっていないかを最初に疑う。is None で早期判定するか、そもそも None を返さない型設計にする。
よくある原因
- インプレース操作の戻り値を代入:
lst = lst.append(x)やs = s.sort()はNoneを返す。
Python のミュータブル系メソッドの典型的な罠。 - 既定値なしの get:
os.environ.get("X")やre.match(pat, s)は値が無い・マッチしないとNone。
続けて.group()などを呼ぶと例外。 - 関数の return 忘れ:
def f(): result = compute()のようにreturnを書き忘れていると、呼び出し側でf().attrが落ちる。 - API の None 返却: Django ORM
qs.first()、requestsのオプショナル属性、SQLAlchemy のsession.get()などは見つからないとNone。
解決策
1. インプレースと関数型を見分ける
# NG: append は None を返す
items = items.append(x)
# OK: 戻り値を使わない or 関数型版を使う
items.append(x)
items = items + [x]2. is None で早期判定
m = re.match(r"(\d+)", s)
if m is None:
return 0
return int(m.group(1))公式ドキュメント のとおり、None に属性は無い前提で書く。
3. typing.Optional と型チェッカ
from typing import Optional
def find_user(uid: int) -> Optional["User"]:
...
user = find_user(1)
if user is None:
raise LookupError
user.name # ここでは User 型に絞られているmypy / pyright を導入すれば、typing.Optional の取り扱い漏れを CI で潰せる。
4. 既定値や raise する版を選ぶ
env = os.environ.get("KEY", "") # 既定値
env = os.environ["KEY"] # 無ければ KeyError
user = User.objects.get(pk=1) # 無ければ DoesNotExist「無いときに静かに None で返す API」と「raise する API」を場面で使い分けると、後段で属性アクセスする側の不安が消える。