- Python
Python bytesとstrの変換|encode・decodeとエラー対処
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期連続の黒字決算。