Pythonでstr(文字列)をbytes(バイト列)に変換するには 文字列.encode("utf-8")、bytesをstrに変換するには バイト列.decode("utf-8") を使います。 strは「文字の並び」、bytesは「0〜255の数値の並び」で、両者をつなぐ対応表が文字コード(エンコーディング)です。変換で起きるエラーのほとんどは、「書き込んだときの文字コード」と「読むときに指定した文字コード」が合っていないことが原因です。

この記事では、変換の基本に加えて、errors引数の全種類を同じバイト列で実行した比較表、Windowsで作られたcp932のCSV・BOM付きUTF-8・途中で切れたバイト列という3つのパターンでのエラー再現、エラーメッセージから直し方を引ける逆引き表、hex・base64・bytearrayとの相互変換のコピペ集をまとめました。実行結果はすべて Python 3.11.15(Linux、既定の文字コードは utf-8)で実際に動かしたものです。

bytesとstrの変換 早見表

やりたいこと 書き方 実行結果
str → bytes(UTF-8) "日本語abc".encode("utf-8") b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9eabc'
str → bytes(cp932) "日本語abc".encode("cp932") b'\x93\xfa\x96{\x8c\xeaabc'
bytes → str b.decode("utf-8") '日本語abc'
bytes → str(別の書き方) str(b, "utf-8") '日本語abc'
str → bytes(別の書き方) bytes("日本語abc", "utf-8") encode と同じ結果
読めないバイトを「�」に置き換えて読む b.decode("utf-8", errors="replace") エラーにならず続行
BOM付きUTF-8を読む open(path, encoding="utf-8-sig") 先頭のBOMが取り除かれる
Windowsで作られたCSVを読む open(path, encoding="cp932", newline="") 「①」「髙」も読める
bytes → 16進数の文字列 b"\x01\xabPy".hex() '01ab5079'
16進数の文字列 → bytes bytes.fromhex("01ab5079") b'\x01\xabPy'
bytes → Base64の文字列 base64.b64encode(b).decode("ascii") '5pel5pys6Kqe'
Base64の文字列 → bytes base64.b64decode("5pel5pys6Kqe") 元のバイト列
書き換えできるバイト列にする bytearray(b) 要素の代入や追加ができる
文字数とバイト数を調べる len(s) / len(s.encode("utf-8")) 「日本語abc」は 6 / 12

encodeとdecodeの向きは「strからbytesがencode、bytesからstrがdecode」です。 迷ったら「人が読める文字を機械向けの符号にするのがエンコード」と覚えると取り違えにくくなります。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python bytesとstrの変換|encode・decodeとエラー対処」

bytesとstrの違い

比べる点 str bytes
中身 文字(Unicodeのコードポイント)の並び 0〜255の整数の並び
リテラルの書き方 "abc"、"日本語" b"abc"(ASCII文字のみ書ける)
x[0] で取れるもの 1文字の str("a") 整数(97)
len() が返すもの 文字数 バイト数
主に出てくる場所 画面表示、文字列の加工 ファイルのバイナリ読み書き、通信、画像、ハッシュ値
変換メソッド encode() で bytes に decode() で str に
書き換え できない(イミュータブル) できない(書き換えたいときは bytearray)
s = "日本語abc"
b = s.encode("utf-8")
print(len(s), len(b), len(s.encode("cp932")))   # 6 12 9
print(b"abc"[0], b"abc"[0:1], list(b"abc"))      # 97 b'a' [97, 98, 99]

同じ「日本語abc」でも、文字数は6、UTF-8では12バイト、cp932では9バイトです。 UTF-8は日本語1文字を主に3バイト、cp932は2バイトで表すためです。bytesの1要素を取り出すと整数になる点も、strとの大きな違いです。1バイト分のbytesが欲しいときは b[0:1] のようにスライスで取ります。

bytesリテラルに日本語を直接書くと、次のエラーになります。

SyntaxError: bytes can only contain ASCII literal characters

日本語のバイト列が必要なときは、"あ".encode("utf-8") のように文字列から作ります。

str → bytes:encodeの使い方

s = "日本語abc"

print(s.encode())            # 引数を省略すると UTF-8
print(s.encode("utf-8"))
print(s.encode("cp932"))
print(s.encode("utf-16"))

実行結果:

b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9eabc'
b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9eabc'
b'\x93\xfa\x96{\x8c\xeaabc'
b'\xff\xfe\xe5e,g\x9e\x8aa\x00b\x00c\x00'
  • encode() の引数を省略すると UTF-8 になります。 str.encode() と bytes.decode() の既定値は、OSに関係なく UTF-8 です。
  • 表示の \xe6 は「16進数でe6の1バイト」を表します。{ や abc のように、ASCII文字に当たるバイトはその文字で表示されます。cp932の結果に { が混ざっているのはそのためです。
  • UTF-16の先頭にある \xff\xfe はBOM(バイトオーダーマーク)で、バイトの並び順を示す目印です。

bytes() で作る書き方もあります。この場合は文字コードの指定が必須です。

print(bytes("日本語abc", "utf-8"))   # encode("utf-8") と同じ
print(bytes("日本語abc"))            # TypeError: string argument without an encoding

bytes → str:decodeの使い方

b = b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9eabc'

print(b.decode())            # 引数を省略すると UTF-8
print(b.decode("utf-8"))
print(str(b, "utf-8"))       # decode と同じ結果
print(str(b))                # 文字コードを省くと「見た目」の文字列になる

実行結果:

日本語abc
日本語abc
日本語abc
b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9eabc'

str(b) のように文字コードを省くと、変換ではなく「b'...' という表示そのもの」が文字列になります。 エラーにならないので気づきにくい箇所です。bytesをstrにするときは b.decode("utf-8") か str(b, "utf-8") と、文字コードを必ず書きましょう。なお、python -bb を付けて実行すると、bytesとstrを比較した箇所などで BytesWarning: Comparison between bytes and string がエラーとして出るため、こうした取り違えを見つけやすくなります(今回確認しました)。

よく使う文字コード名

指定する名前 内容 主に出会う場面
utf-8 現在もっとも広く使われる文字コード Webページ、JSON、ソースコード、Linux・macOSのファイル
utf-8-sig 先頭にBOM(3バイト)が付いたUTF-8 Excelで開けるように保存されたCSV、Windowsのメモ帳で保存したファイル
cp932 Windowsの日本語環境で使われるShift_JISの拡張版 Excelで「CSV(コンマ区切り)」として保存したファイル
shift_jis JIS規格のShift_JIS。「①」「髙」などは含まれない 規格どおりのShift_JISが求められるとき
euc_jp EUC-JP 以前から使われているUnix系のシステムのデータ
utf-16 1文字を主に2バイトで表す Windowsの一部のツールの出力
ascii 英数字と記号のみ(0〜127) 英数字だけと決まっているデータ
latin-1 0〜255をそのまま1文字に対応させる 西ヨーロッパの言語のデータ

Python公式ドキュメントのcodecsモジュールのページには、cp932の別名として 932、ms932、mskanji、ms-kanji、windows-31j、shift_jisの別名として csshiftjis、shiftjis、sjis、s_jis が載っています(今回ページを開いて確認しました)。存在しない名前を指定すると LookupError: unknown encoding: utf8mb4 のようなエラーになります。

errors引数の比較表(同じバイト列で実行)

encodeとdecodeには、変換できない箇所に出会ったときの動作を選ぶ errors 引数があります。既定値は strict(エラーにする)です。

decodeのerrors:読めないバイトの扱い

次のバイト列を用意しました。「注文」のあとに不正なバイト \xff、「OK」、最後に「完了」の途中で切れた2バイト(\xe5\xae)が続きます。

data = "注文".encode("utf-8") + b"\xff" + b"OK" + "完了".encode("utf-8")[:2]
print(data)   # b'\xe6\xb3\xa8\xe6\x96\x87\xffOK\xe5\xae'

for handler in ("strict", "ignore", "replace", "backslashreplace", "surrogateescape"):
    try:
        print(handler, "=>", repr(data.decode("utf-8", errors=handler)))
    except UnicodeDecodeError as e:
        print(handler, "=>", e)
errors 実行結果 動作 向いている場面
strict(既定) UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 6: invalid start byte エラーにして止める ふだんはこれ。文字コードの取り違えにすぐ気づける
ignore '注文OK' 読めないバイトを黙って捨てる 欠けてもよいログの検索など。データが減ったことに気づけない点に留意
replace '注文�OK�' 読めない箇所を「�」(U+FFFD)にする 画面表示やログ出力で、欠けた場所を残したいとき
backslashreplace '注文\\xffOK\\xe5\\xae' 読めないバイトを \xff の形の文字にする 調査用。どのバイトが読めなかったかをそのまま確認できる
surrogateescape '注文\udcffOK\udce5\udcae' 読めないバイトを特別な文字(U+DC80〜U+DCFF)に置き換える 元のバイト列に完全に戻したいとき(下で説明)
xmlcharrefreplace TypeError: don't know how to handle UnicodeDecodeError in error callback decodeでは使えない encode専用
namereplace 同上の TypeError decodeでは使えない encode専用

同じバイト列でも、errorsの選び方で結果が大きく変わります。 ignore は「注文OK」となり、何かが欠けたことが結果から読み取れません。調査や表示では replace か backslashreplace を選ぶと、欠けた位置があとから追えます。

surrogateescapeは元のバイト列に戻せる

surrogateescape で読んだ文字列は、同じ指定でencodeすると元のバイト列に戻ります。

text = data.decode("utf-8", errors="surrogateescape")
print(text.encode("utf-8", errors="surrogateescape") == data)   # True
print(text.encode("utf-8"))

実行結果:

True
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcff' in position 2: surrogates not allowed

中身を壊さずに読んで、そのまま書き戻す用途(ファイル名や、一部だけ文字コードが混ざったテキストの加工)に向いています。errorsを付けずにencodeすると、上のように UnicodeEncodeError になります。

encodeのerrors:その文字コードにない文字の扱い

今度は逆向きです。顔の絵文字・「髙」・ユーロ記号を含む "A😀髙€" を、cp932とasciiに変換しました。

for enc in ("cp932", "ascii"):
    for handler in ("strict", "ignore", "replace", "backslashreplace",
                    "xmlcharrefreplace", "namereplace"):
        try:
            print(enc, handler, "=>", "A😀髙€".encode(enc, errors=handler))
        except UnicodeEncodeError as e:
            print(enc, handler, "=>", e)
errors cp932への変換結果 asciiへの変換結果
strict(既定) UnicodeEncodeError: 'cp932' codec can't encode character '\U0001f600' in position 1: illegal multibyte sequence UnicodeEncodeError: 'ascii' codec can't encode characters in position 1-3: ordinal not in range(128)
ignore b'A\xee\xe0' b'A'
replace b'A?\xee\xe0?' b'A???'
backslashreplace b'A\\U0001f600\xee\xe0\\u20ac' b'A\\U0001f600\\u9ad9\\u20ac'
xmlcharrefreplace b'A😀\xee\xe0€' b'A😀髙€'
namereplace b'A\\N{GRINNING FACE}\xee\xe0\\N{EURO SIGN}' b'A\\N{GRINNING FACE}\\N{CJK UNIFIED IDEOGRAPH-9AD9}\\N{EURO SIGN}'
  • cp932には「髙」があるので \xee\xe0 に変換でき、絵文字とユーロ記号だけが置き換えの対象になっています。
  • encodeの replace は「?」、decodeの replace は「�」と、向きによって置き換える文字が違います。
  • xmlcharrefreplace は 😀 のようなHTML・XMLの数値文字参照にします。HTMLに埋め込む文字列を作るときに使えます。
  • surrogateescape をencodeで指定しても、通常の文字(絵文字など)は対象外で、strictと同じエラーになりました。

なお、公式ドキュメントには surrogatepass(UTF-8・UTF-16・UTF-32でサロゲートのコードポイントをそのまま通す)も載っています。上のdecodeの例に指定したところ、\xff は対象外のため strict と同じ UnicodeDecodeError になりました。

パターン1:Windowsのcp932 CSVを読む

UnicodeDecodeErrorを再現する

Excelで「CSV(コンマ区切り)」として保存したファイルは、日本語のWindowsではcp932になるのが一般的です。同じ内容のファイルをPythonで作って確かめます。

with open("members_cp932.csv", "w", encoding="cp932", newline="") as f:
    f.write("会員ID,氏名,備考\r\n1,田中,①\r\n2,髙橋,継続\r\n")

with open("members_cp932.csv", encoding="utf-8") as f:
    print(f.read())

実行結果:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte

'utf-8' codec can't decode byte 0x8x〜0x9x ... invalid start byte は、cp932(Shift_JIS系)のファイルをUTF-8として読んだときの代表的なメッセージです。 cp932の日本語は1バイト目が 0x81〜0x9f か 0xe0〜0xfc で始まり、その多くはUTF-8の先頭バイトとして使えない値だからです。

直し方:encoding="cp932" を指定する

import csv

with open("members_cp932.csv", encoding="cp932", newline="") as f:
    for row in csv.reader(f):
        print(row)

実行結果:

['会員ID', '氏名', '備考']
['1', '田中', '①']
['2', '髙橋', '継続']

csvモジュールでファイルを開くときは newline="" も合わせて指定します。

shift_jis ではなく cp932 を指定する理由

同じファイルを encoding="shift_jis" で開くと、次のエラーになりました。

UnicodeDecodeError: 'shift_jis' codec can't decode byte 0x87 in position 25: illegal multibyte sequence

「①」のバイト(\x87@)が、規格どおりのShift_JISには含まれないためです。文字ごとの違いを表にします。

文字 encode("cp932") encode("shift_jis")
①(丸数字) b'\x87@' UnicodeEncodeError(illegal multibyte sequence)
髙(はしごだか) b'\xee\xe0' UnicodeEncodeError
~(全角チルダ U+FF5E) b'\x81`' UnicodeEncodeError
〜(波ダッシュ U+301C) UnicodeEncodeError b'\x81`'

Windowsで作られた「Shift_JIS」のファイルは、ほとんどの場合cp932です。 読むときも書くときも cp932 を指定しておくと、丸数字や「髙」「﨑」でエラーになることを避けられます。「〜」は見た目が似た2つの文字があり、cp932に書き出すなら全角チルダ(U+FF5E)のほうを使います。

逆のパターン:UTF-8のファイルをcp932で読む

UTF-8のファイルをcp932として読むと、エラーになる場合と、エラーにならず文字化けする場合があります。

print("こんにちは".encode("utf-8").decode("cp932"))
print("会員ID,氏名".encode("utf-8").decode("cp932"))
print("在庫は3個です".encode("utf-8").decode("cp932"))

実行結果:

縺薙s縺ォ縺。縺ッ
莨壼藤ID,豌丞錐
UnicodeDecodeError: 'cp932' codec can't decode byte 0x99 in position 18: incomplete multibyte sequence

「縺」「繧」「莨」などの漢字や半角カナが並ぶ文字化けは、UTF-8をcp932で読んだ合図です。 encoding="utf-8" に直すと解消します。

Windowsでは、open() の encoding を省略するとOSの設定に合わせた文字コード(日本語環境ではcp932)が使われる、という動作が広く知られています(Windowsでは未実行です。今回のLinux環境では locale.getpreferredencoding(False) が utf-8 でした)。どのOSでも同じ結果になるよう、open() には毎回 encoding を書くのがおすすめです。python -X warn_default_encoding を付けて実行すると、encoding を省略した open() に対して EncodingWarning: 'encoding' argument not specified という警告が出るので、書き忘れた箇所を探せます(今回確認しました)。

パターン2:BOM付きUTF-8(utf-8-sig)

先頭の列名だけ取り出せない現象を再現する

BOMは、ファイルの先頭に置かれる目印のバイトです。UTF-8のBOMは \xef\xbb\xbf の3バイトで、ExcelがUTF-8のCSVを開くときの目印にもなっています。

import csv

with open("items_bom.csv", "w", encoding="utf-8-sig", newline="") as f:
    f.write("品名,数量\r\nノート,3\r\n")

print(open("items_bom.csv", "rb").read()[:12])

with open("items_bom.csv", encoding="utf-8", newline="") as f:
    rows = list(csv.DictReader(f))
print(rows)
print(rows[0]["品名"])

実行結果:

b'\xef\xbb\xbf\xe5\x93\x81\xe5\x90\x8d,\xe6\x95'
[{'品名': 'ノート', '数量': '3'}]
KeyError: '品名'

encoding="utf-8" で読んでもエラーにはなりませんが、先頭に見えない文字  が残ります。 その結果、1列目の列名が「品名」になり、rows[0]["品名"] が KeyError になります。画面に表示しても見えないため、原因に気づきにくいパターンです。

直し方:encoding="utf-8-sig" で読む

with open("items_bom.csv", encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))
print(rows[0]["品名"])                                   # ノート

print("品名".encode("utf-8").decode("utf-8-sig"))        # BOMなしでも読める → 品名
print("品名".encode("utf-8-sig"))                        # b'\xef\xbb\xbf\xe5\x93\x81\xe5\x90\x8d'

utf-8-sig は、BOMがあれば取り除き、なければそのまま読みます。 BOMの有無がわからないUTF-8のファイルは、utf-8-sig で読んでおくとどちらにも対応できます。公式ドキュメントにも「エンコード時はBOMを先頭に付け、デコード時は先頭のBOMがあれば読み飛ばす」という趣旨の説明があります(今回確認しました)。

場面 指定する encoding 理由
UTF-8のCSVを読む(BOMの有無が不明) utf-8-sig どちらでも正しく読める
Excelで開くCSVをUTF-8で書き出す utf-8-sig BOMがあるとExcelがUTF-8と判断しやすい
プログラム同士で受け渡すファイルを書く utf-8 BOMなしが一般的。受け取る側の処理が単純になる
JSONを書き出す utf-8 下のとおり、BOM付きは読み込み側でエラーになることがある

BOMに関連するほかのエラー

import json
json.loads(b'\xef\xbb\xbf{"a":1}'.decode("utf-8"))
json.decoder.JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)

エラーメッセージに直し方が書かれています。.decode("utf-8-sig") に変えると {'a': 1} が得られました。

また、BOMが残った文字列をcp932で書き出そうとすると、次のエラーになります。

UnicodeEncodeError: 'cp932' codec can't encode character '' in position 0: illegal multibyte sequence

'' がメッセージに出てきたら、読み込み側を utf-8-sig にするのが近道です。

パターン3:途中で切れたバイト列

エラーを再現する

通信や大きなファイルを、決まったバイト数ずつ読むと、日本語1文字(UTF-8で3バイト)の途中で区切られることがあります。

full = "在庫管理".encode("utf-8")      # 12バイト
print(full[:7].decode("utf-8"))

実行結果:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe7 in position 6: unexpected end of data

切れ方によってメッセージの末尾が変わります。

切れ方 実行したコード メッセージの末尾
文字の途中で終わっている full[:7].decode("utf-8") unexpected end of data
文字の途中に別のデータが続いている (full[:7] + b"!").decode("utf-8") invalid continuation byte
文字の途中から始まっている full[1:].decode("utf-8") invalid start byte(byte 0x9c in position 0)
cp932で文字の途中で終わっている "在庫".encode("cp932")[:3].decode("cp932") incomplete multibyte sequence

直し方1:全部つなげてからdecodeする

データが一度に収まる大きさなら、bytesのまま結合し、最後に1回だけdecodeします。

chunks = [full[i:i + 5] for i in range(0, len(full), 5)]   # 5バイトずつに分かれた受信データの例
text = b"".join(chunks).decode("utf-8")
print(text)   # 在庫管理

直し方2:インクリメンタルデコーダで少しずつ変換する

届いた分から順に文字にしたいときは、codecs.getincrementaldecoder() を使います。文字の途中で切れた分を内部に取っておき、続きが来たら合わせて変換してくれます。

import codecs

decoder = codecs.getincrementaldecoder("utf-8")()
for i in range(0, len(full), 5):
    chunk = full[i:i + 5]
    print(chunk, "→", repr(decoder.decode(chunk)))
print("最後 →", repr(decoder.decode(b"", final=True)))

実行結果:

b'\xe5\x9c\xa8\xe5\xba' → '在'
b'\xab\xe7\xae\xa1\xe7' → '庫管'
b'\x90\x86' → '理'
最後 → ''

同じ5バイトずつの区切りを、区切りごとに chunk.decode("utf-8") すると can't decode bytes in position 3-4: unexpected end of data になりました。ファイルの場合は、open(path, encoding="utf-8") で開いて読めば、Pythonが内部で同じ処理を行うので、文字の途中で切れることを気にせずに済みます。

full[:7].decode("utf-8", errors="ignore") と書けば '在庫' が得られますが、切れた文字は失われます。途中の区切りには ignore ではなく、結合かインクリメンタルデコーダを使うと、文字が欠けません。

エラーメッセージ別の直し方(逆引き)

メッセージはすべて今回実際に出力されたものです。

UnicodeDecodeError(bytes → str で起きる)

メッセージ(抜粋) 主な原因 直し方
'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte(0x81〜0x9f、0xe0〜0xfcなど) cp932のデータをUTF-8で読んだ encoding="cp932" を指定
'utf-8' codec can't decode byte 0xff in position 0: invalid start byte UTF-16のデータ(先頭が \xff\xfe)をUTF-8で読んだ、またはバイナリデータ encoding="utf-16"。画像などはテキストにせず bytes のまま扱う
'utf-8' codec can't decode byte ... : unexpected end of data 文字の途中でバイト列が終わっている 結合してからdecode、またはインクリメンタルデコーダ
'utf-8' codec can't decode byte ... : invalid continuation byte 文字の途中に別のデータが入っている、または別の文字コード 区切り位置と文字コードを確認
'shift_jis' codec can't decode byte 0x87 ... : illegal multibyte sequence 丸数字などcp932の拡張文字が含まれる shift_jis を cp932 に変える
'cp932' codec can't decode byte 0xef in position 0: illegal multibyte sequence BOM付きUTF-8をcp932で読んだ encoding="utf-8-sig"
'cp932' codec can't decode byte ... : incomplete multibyte sequence UTF-8をcp932で読んだ、または途中で切れている encoding="utf-8"、区切り位置を確認
'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128) 日本語を含むデータをasciiで読んだ encoding="utf-8" など実際の文字コードを指定

UnicodeEncodeError(str → bytes で起きる)

メッセージ(抜粋) 主な原因 直し方
'cp932' codec can't encode character '\U0001f600' ... : illegal multibyte sequence 絵文字などcp932にない文字を書き出そうとした 出力をUTF-8(Excel向けなら utf-8-sig)にする。cp932が必須なら errors="replace" などを選ぶ
'cp932' codec can't encode character '' in position 0 BOMが文字列の先頭に残っている 読み込みを utf-8-sig にする
'shift_jis' codec can't encode character '髙'(髙)、'①'(①) 規格どおりのShift_JISにない文字 cp932 を指定
'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) 日本語をasciiに変換しようとした encode("utf-8") にする
'latin-1' codec can't encode characters ... : ordinal not in range(256) 日本語をlatin-1に変換しようとした encode("utf-8") にする
'utf-8' codec can't encode character '\udcff' ... : surrogates not allowed surrogateescape で読んだ文字列を通常のencodeにかけた encode("utf-8", errors="surrogateescape") で戻す

型の取り違えで起きるエラー

メッセージ 実行したコード 直し方
TypeError: can only concatenate str (not "bytes") to str "a" + b"b" "a" + b"b".decode("utf-8")
TypeError: can't concat str to bytes b"a" + "b" b"a" + "b".encode("utf-8")
TypeError: a bytes-like object is required, not 'str' "a" in b"abc"、open(p, "wb").write("abc")、base64.b64encode("日本語") strを .encode() してから渡す
TypeError: write() argument must be str, not bytes open(p, "w").write(b"abc") モードを "wb" にする、または .decode() してから書く
AttributeError: 'str' object has no attribute 'decode' "abc".decode("utf-8") すでにstrなのでdecodeは不要
AttributeError: 'bytes' object has no attribute 'encode' b"abc".encode("utf-8") すでにbytesなのでencodeは不要
TypeError: string argument without an encoding bytes("日本語") bytes("日本語", "utf-8")
LookupError: unknown encoding: utf8mb4 "a".encode("utf8mb4") Pythonでの名前は utf-8

「いま手元にあるのはstrかbytesか」を type(x) で確かめると、ほとんどの取り違えはすぐ解決します。 なお、b"abc" == "abc" はエラーにならず False になります。比較が常に成り立たないときは、片方がbytesのままになっていないか確認してみてください。

文字コードがわからないファイルを読む関数

候補を順に試し、最初に読めた文字コードを採用する小さな関数です。

def read_text_auto(path, encodings=("utf-8-sig", "cp932")):
    """候補の文字コードを順に試して (本文, 使った文字コード) を返す"""
    raw = open(path, "rb").read()
    for enc in encodings:
        try:
            return raw.decode(enc), enc
        except UnicodeDecodeError:
            pass
    return raw.decode("utf-8", errors="replace"), "utf-8(replace)"

print(read_text_auto("members_cp932.csv")[1])   # cp932
print(read_text_auto("items_bom.csv")[1])       # utf-8-sig

UTF-8を先に試すのがコツです。 cp932の日本語がたまたまUTF-8として正しく読めることはまれなので、UTF-8で読めたらUTF-8と判断できます。逆の順にすると、UTF-8のデータが文字化けしたまま通ってしまうことがあります。

bytes・bytearray・hex・base64の相互変換コピペ集

16進数(hex)との変換

b = b"\x01\xabPy"

print(b.hex())                      # '01ab5079'
print(b.hex(" "))                   # '01 ab 50 79'   区切り文字つき
print(b.hex(":", 2))                # '01ab:5079'     2バイトごとに区切る
print(bytes.fromhex("01ab5079"))    # b'\x01\xabPy'
print(bytes.fromhex("01 ab 50 79")) # b'\x01\xabPy'   空白は無視される

16進数として読めない文字や、桁数が奇数の文字列を渡すと、次のエラーになります。

ValueError: non-hexadecimal number found in fromhex() arg at position 1

Base64との変換

Base64は、バイト列を英数字と一部の記号だけで表す方式です。b64encode の引数も戻り値も bytes なので、文字列として扱うには前後で encode / decode を挟みます。

import base64

encoded = base64.b64encode("日本語".encode("utf-8"))
print(encoded)                                   # b'5pel5pys6Kqe'
print(encoded.decode("ascii"))                   # '5pel5pys6Kqe'  ← str として使う形

print(base64.b64decode("5pel5pys6Kqe").decode("utf-8"))   # 日本語

print(base64.b64encode(b"\xfb\xff\xfe"))         # b'+//+'
print(base64.urlsafe_b64encode(b"\xfb\xff\xfe")) # b'-__-'  URLやファイル名向け
起きること 実行したコード 直し方
TypeError: a bytes-like object is required, not 'str' base64.b64encode("日本語") "日本語".encode("utf-8") を渡す
binascii.Error: Incorrect padding base64.b64decode("5pel5pys6Kq")(1文字欠けている) 文字列が途中で切れていないか確認する

bytearray(書き換えできるバイト列)

ba = bytearray(b"abc")
ba[0] = 65                      # 1バイト書き換え(65 は "A")
ba += b"!"                      # 末尾に追加
ba.extend("日".encode("utf-8"))
print(ba)                       # bytearray(b'Abc!\xe6\x97\xa5')
print(bytes(ba))                # b'Abc!\xe6\x97\xa5'
print(ba.decode("utf-8"))       # Abc!日
print(bytearray("日本", "utf-8"))   # bytearray(b'\xe6\x97\xa5\xe6\x9c\xac')

bytesは書き換えできないため、x = b"abc"; x[0] = 65 は TypeError: 'bytes' object does not support item assignment になります。少しずつ追記・書き換えをするなら bytearray、完成したら bytes() で固定という使い分けができます。

整数・リスト・コードポイントとの変換

print(bytes([97, 98, 99]))                 # b'abc'           整数のリスト → bytes
print(list(b"abc"))                        # [97, 98, 99]     bytes → 整数のリスト
print(bytes(3))                            # b'\x00\x00\x00'  ゼロで埋めた3バイト
print((1024).to_bytes(2, "big"))           # b'\x04\x00'      整数 → bytes(上位バイトが先)
print((1024).to_bytes(2, "little"))        # b'\x00\x04'      整数 → bytes(下位バイトが先)
print(int.from_bytes(b"\x04\x00", "big"))  # 1024             bytes → 整数
print(ord("あ"), hex(ord("あ")), chr(0x3042))   # 12354 0x3042 あ

ファイル・外部コマンドとの受け渡し

import io
import subprocess

# バイナリモードは bytes、テキストモードは str
raw = open("items_bom.csv", "rb").read()                        # bytes
text = open("items_bom.csv", encoding="utf-8-sig").read()       # str

# メモリ上のバイト列をテキストとして読む
print(io.TextIOWrapper(io.BytesIO("日本".encode("utf-8")), encoding="utf-8").read())   # 日本

# 外部コマンドの出力
print(subprocess.run(["echo", "日本"], capture_output=True).stdout)
# b'\xe6\x97\xa5\xe6\x9c\xac\n'
print(repr(subprocess.run(["echo", "日本"], capture_output=True, text=True, encoding="utf-8").stdout))
# '日本\n'
相手 受け取る型 strにする方法
open(path, "rb") bytes .decode(文字コード)
open(path, encoding=...) str 変換済み
subprocess.run(..., capture_output=True) bytes text=True, encoding="utf-8" を付ける
ソケットなど通信の受信データ bytes 結合してから .decode()
base64.b64encode() bytes .decode("ascii")
bytes.hex() str 変換済み

よくある質問

Q. encodeとdecodeはどちらがどちらの向きですか? A. strからbytesにするのがencode、bytesからstrにするのがdecodeです。"日本語".encode("utf-8") でバイト列になり、そのバイト列に .decode("utf-8") を使うと元の文字列に戻ります。向きを逆にすると AttributeError: 'str' object has no attribute 'decode' になります。

Q. UnicodeDecodeErrorが出たとき、errors="ignore" を付ければ解決しますか? A. エラーは出なくなりますが、読めなかった部分が黙って消えます。今回の例では '注文OK' となり、欠けた位置がわかりませんでした。まずは実際の文字コード(cp932、utf-8-sig など)を指定し、どうしても読めない箇所が残るときに replace や backslashreplace を選ぶと、欠けた位置を追えます。

Q. cp932とshift_jisはどちらを指定すればよいですか? A. Windowsで作られたファイルなら cp932 を指定します。「①」「髙」などは cp932 では変換できましたが、shift_jis では UnicodeEncodeError・UnicodeDecodeError になりました。

Q. CSVの1列目の列名だけ取り出せないのはなぜですか? A. ファイル先頭のBOMが列名の頭に残っている可能性があります。今回の再現では列名が '品名' になり、KeyError: '品名' が出ました。encoding="utf-8-sig" で開くと解消します。

まとめ

  • strからbytesは encode()、bytesからstrは decode()。 引数を省略すると UTF-8
  • str(b) は変換にならない。 b.decode("utf-8") か str(b, "utf-8") と文字コードを書く
  • errorsの既定は strict。 ignore は黙って捨てる、replace は「�」や「?」に置換、backslashreplace はバイトの値を残す、surrogateescape は元のバイト列に戻せる
  • invalid start byte はcp932のデータをUTF-8で読んだ合図。 Windowsで作られたCSVは encoding="cp932"
  • '' が出てきたらBOM。 読み込みを utf-8-sig にする
  • unexpected end of data は文字の途中で切れている。 結合してからdecodeするか、インクリメンタルデコーダを使う
  • hexは b.hex() と bytes.fromhex()、Base64は bytes を渡して bytes を受け取る、書き換えたいときは bytearray

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python bytesとstrの変換|encode・decodeとエラー対処」

株式会社RJCでは、Pythonの文字コードやバイト列の扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?