◀ 5.

PythonのstrとNone連結エラー|原因の切り分けと安全な対処

▶

結論:str()の失敗ではなく、文字列へNoneを直接連結した

"Hello, " + NoneではTypeErrorになります。一方str(None)自体は文字列"None"を返します。まず値の返り元とNoneが許可される仕様かを確認し、例外を消すだけの空文字/f-stringへの置換で必須値の欠落を隠さないようにします。

値の生成元を調べ、Noneの許可と表示変換を区別して連結する判断図

値を出力する前の判断

入力/操作区別すること
欠落キーdict.getのdefaultはキーがない場合。明示Noneとは別
None任意値の代替表示か、必須値不正として拒否かを決める
空文字/0/Falseorはこれらも置換する。Noneだけの判定とは違う
str/f-string表示形式を変えるだけで、欠けた値を復元しない
re.match失敗None.groupの呼出しなら、連結前にAttributeError
list.append戻り値はNone。元listの変更と表示対象を分ける

任意表示・明示null・必須設定を分けた実行例

次はPython3.12.10の標準ライブラリだけの例です。optional_labelはNoneだけをGuestにし、空文字は保持します。nickname_or_guestは欠落キーをGuestにしますが、明示Noneを拒否します。これは一例の仕様で、業務上nullを許可する場合はその条件を明示してください。required_hostは必須値を黙ってlocalhostに変えず拒否し、実通信や環境変数の変更はしません。

import re


def optional_label(value: str | None, fallback: str = 'Guest') -> str:
    if value is None:
        return fallback
    if not isinstance(value, str):
        raise TypeError('label must be str or None')
    return value


def nickname_or_guest(user: dict) -> str:
    if 'nickname' not in user:
        return 'Guest'
    value = user['nickname']
    if value is None:
        raise ValueError('nickname was explicitly null')
    if not isinstance(value, str):
        raise TypeError('nickname must be str')
    return value


def required_host(settings: dict) -> str:
    value = settings.get('HOST')
    if value is None or (isinstance(value, str) and not value.strip()):
        raise ValueError('HOST is required')
    if not isinstance(value, str):
        raise TypeError('HOST must be str')
    return value


def found_number(text: str) -> str:
    match = re.match(r'(\d+)', text)
    if match is None:
        return 'Not found'
    return 'Found: ' + match.group(1)


if __name__ == '__main__':
    print('Hello, ' + optional_label(None))
    print('Empty stays empty:', repr(optional_label('')))
    print('Missing key:', nickname_or_guest({}))
    print(found_number('123abc'))
    print(found_number('abc'))

実行例の結果はHello, Guest、空文字は''、欠落キーはGuest、regexはFound: 123/Not foundでした。型ヒントだけでは実行時に型が強制されないため、この例では必要な型判定も書いています。秘密値を含む実際の設定全文をprintして調査しません。

旧コードを読む際の訂正

既存の10コード例と元の5黒コード表は保持します。dict.getにdefaultを書いても、キーが存在して値がNoneならdefaultは使われません。name or "ゲスト"はNone限定ではなく空文字等も置き換えます。戻り値がない関数と未定義変数のNameError、未マッチのregexのAttributeErrorを同じTypeErrorとして扱いません。

通常のf"{None}"は表示できますが、すべてのformat指定がNoneに使えるわけではありません。検体では幅指定f"{None:>8}"はTypeErrorでした。format/strが通ることと、入力の型/必須性/意味が正しいことは別です。Pythonの+全般を「同じ型しか使えない」と一般化もしません。

調査から再発防止まで

  1. tracebackで失敗した演算と値の生成元を特定する。実値に秘密情報がある場合は型/有無など必要項目だけを記録する。
  2. 戻り値のすべての経路、dictの欠落/明示None、設定の未指定等を確認する。Noneを空文字へ変える前に仕様を決める。
  3. 許可する場合だけ代替表示し、必須値/型違いは適切な場所で拒否する。広いexceptで握りつぶさない。
  4. None/欠落/空文字/正常値/不正型の試験を追加する。型ヒント/静的検査だけで入力検証を完了したとは扱わない。

エラー分類はPythonエラー一覧を参照。既存の辞書型を文字列へ変換する方法も保持しますが、辞書の文字列化は欠落値の修復ではありません。

既存のパターン別資料

この記事の要点
  • TypeError: can only concatenate str (not "NoneType") to str はNone を文字列と + で連結したエラー
  • 原因: 関数が return を書き忘れた / メソッドが None を返した / 値が明示Noneになっている(未定義名のNameErrorとは別)
  • 対処 ①: 連結前に None チェック
  • 対処 ②: str() で明示的に文字列化
  • 対処 ③: f-string や format() なら None も "None" として表示される(エラーにならない)

 

エラーの意味

Python で以下のようなコードを書くと発生:

旧例の注意:strにNoneを直接足す例です。str(None)自体が失敗するという意味ではありません。

name = None
greeting = "Hello, " + name
# TypeError: can only concatenate str (not "NoneType") to str

ここでの文字列連結はstrとNoneを直接足せないという意味です。Pythonの+全般を同じ型だけと一般化せず、intとfloat等の演算と分けます。文字列 "Hello, " と None を直接 + するとこのエラーになります。

よくある原因パターン

パターン 1: 関数の return 書き忘れ

旧例の注意:通常の関数でreturnを省略した経路はNoneになります。すべての戻り経路と呼出し側の必須性を確認します。

def get_name():
    name = "Alice"
    # return を書き忘れ → 戻り値は None

result = "User: " + get_name()
# TypeError: ...

# 修正:
def get_name():
    name = "Alice"
    return name  # ← 追加

パターン 2: dict.get() で存在しないキー

旧例の注意:dict.getのdefaultは欠落キーに使われます。キーが存在して値がNoneなら、この修正だけでは解決しません。

user = {"name": "Alice"}
greeting = "Hello, " + user.get("nickname")
# get() は存在しないキーで None を返す → TypeError

# 修正:
greeting = "Hello, " + user.get("nickname", "ゲスト")  # デフォルト値

パターン 3: 環境変数が未設定

旧例の注意:未設定と空文字/不正値を区別します。HOSTが必須ならlocalhostへ黙って変更せず、必要な場所で拒否します。実環境の秘密値は公開しません。

import os
host = os.environ.get("HOST")  # 環境変数 HOST が未設定なら None
url = "http://" + host + "/api"
# TypeError

# 修正:
host = os.environ.get("HOST", "localhost")  # デフォルト値

パターン 4: 関数呼び出しが副作用のみ(list.append 等)

旧例の注意:この元例はstr(appendの戻り値)なので連結自体は成功し、Items: Noneになります。listの変更と戻り値を別に扱います。

items = []
result = "Items: " + str(items.append("a"))
# list.append() は None を返す → str(None) は "None" だが、生 None を + すると TypeError

# 修正:
items.append("a")
result = "Items: " + str(items)

パターン 5: 正規表現で match しない

旧例の注意:未マッチのm.group(1)は、連結より先にAttributeErrorになります。TypeErrorと同じ原因/例外として案内せず、m is Noneを先に確認します。

import re
m = re.match(r"(\d+)", "abc")  # マッチしないと None
value = "Found: " + m.group(1)
# m is None → AttributeError もしくは TypeError

# 修正:
m = re.match(r"(\d+)", "abc")
if m:
    value = "Found: " + m.group(1)
else:
    value = "Not found"

対処方法

方法 1: None チェック

旧例の注意:is Noneとorは同じ条件ではありません。orは空文字/0/False等も置換します。空文字を残す仕様ならNoneだけを判定します。

name = get_name()
if name is None:
    name = "ゲスト"
greeting = "Hello, " + name

# または or 演算子で短く
greeting = "Hello, " + (name or "ゲスト")

方法 2: str() で明示変換

旧例の注意:文字列Noneへ表示変換できても、必須値の欠落は直りません。意図した画面/CSV表現かを確認します。

name = get_name()  # None かもしれない
greeting = "Hello, " + str(name)
# → "Hello, None" (エラーにならない)

方法 3: f-string を使う(表示形式の選択)

旧例の注意:通常のf-stringの表示例であり、全format指定/任意objectで例外が出ない保証ではありません。欠落値の意味を先に確認します。

name = None
greeting = f"Hello, {name}"
# → "Hello, None" (エラーにならない、自動で str 化)

方法 4: format() メソッド

旧例の注意:format()も表示形式の選択です。必須性/型の検証の代わりにせず、使用するformat指定を別に確認します。

name = None
greeting = "Hello, {}".format(name)
# → "Hello, None"

類似エラー(要注意)

エラーメッセージ意味
can only concatenate str (not "int") to strint を文字列に連結(str(int_val) で変換)
can only concatenate list (not "str") to listlist と str を連結(list + [str] で対応)
unsupported operand type(s) for +: 'NoneType' and 'str'左辺が None の場合(順序逆)
AttributeError: 'NoneType' object has no attribute ...None に対してメソッド呼び出し

予防策

  • 型ヒント (Type Hints) を使う: 関数の戻り値型を明示すれば IDE が None の可能性を警告
  • mypy で静的型チェック: Optional[str] 等を使い分けて事前に検出
  • None を返す関数は明示的にチェック: dict.get, re.match, list.append等。Pythonのlist.findというAPIはない
  • 値の仕様を明示する: None/空文字/空listを別の意味として扱い、初期値を変えるだけで欠落を隠さない
  • f-string を活用: 表示変換と必須値/型の検証を分ける。すべてのformat指定がNoneへ使えるわけではない

関連記事

元の書式付き資料

元例の注意:元のPython例です。get_name/fetch_nameは説明用の未定義関数です。orはNone以外のfalsy値も置換するため、必須値を空文字へ隠す定型対処には使いません。

can only concatenate str (not "NoneType") to str

元例の注意:元のPython例です。get_name/fetch_nameは説明用の未定義関数です。orはNone以外のfalsy値も置換するため、必須値を空文字へ隠す定型対処には使いません。

name = None

message = "user: " + name

元例の注意:元のPython例です。get_name/fetch_nameは説明用の未定義関数です。orはNone以外のfalsy値も置換するため、必須値を空文字へ隠す定型対処には使いません。

name = get_name()

if name is None:

name = ""

message = "user: " + name

元例の注意:元のPython例です。get_name/fetch_nameは説明用の未定義関数です。orはNone以外のfalsy値も置換するため、必須値を空文字へ隠す定型対処には使いません。

name = get_name() or ""

message = "user: " + name

元例の注意:元のPython例です。get_name/fetch_nameは説明用の未定義関数です。orはNone以外のfalsy値も置換するため、必須値を空文字へ隠す定型対処には使いません。

def get_name():

value = fetch_name()

return value if value is not None else ""

確認範囲と一次資料

Python3.12.10と専用venvのNotebook7.6.3/Server2.21.1、空設定/専用rootのローカル検体で確認した範囲です。本番や利用者の設定/保存済みnotebook・kernel・認証を変更していません。全版/全拡張/実ブラウザ・SSH/conda/全OS・実際のkernel修復やセル実行の保証ではありません。

子ページ

子ページはありません

同階層のページ
  1. ModuleNotFoundError: No module named '~'; '~' is not a package
  2. pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available
  3. ...AppData/Local/Microsoft/WindowsApps/python: Permission denied
  4. 【pycharm】connecting to console が終わらない
  5. can only concatenate str (not "NoneType") to str
  6. can only concatenate str (not "datetime.datetime") to str

最近更新/作成されたページ