閉じる

Pythonの文字とバイトの違いを基礎から解説(encode/decode)

日々のPythonプログラミングでは日本語を含むテキストを扱う場面が多いですが、表示や保存の段階で文字化けに悩む方は少なくありません。

この記事では「文字(str)」と「バイト(bytes)」の違い、そしてencode/decodeの使い方を、手を動かして理解できるように丁寧に解説します。

Pythonの文字(str)とバイト(bytes)の違い

文字(str)はUnicodeのテキスト

Python 3のstrUnicodeテキストです。

つまり、内部的には「あ」や「é」などの文字そのものを表し、特定の保存形式(UTF-8などのバイト列)とは切り離されて管理されています。

画面に表示したり、文字数を数えたりする操作はstrで行います。

バイト(bytes)は生のバイト列(バイナリ)

一方、bytes0〜255の数値(バイト)が並んだ配列です。

画像や圧縮ファイルなどのバイナリデータはもちろん、テキストも保存や送受信の段階では最終的にbytesになります。

文字とバイトは別物であり、必要に応じて相互変換(エンコード/デコード)します。

型とリテラルの書き方(b’…’, bytearray)

bytesb'...'というリテラルで書けます。

変更可能な類似型bytearrayもあります。

Python
# str は Unicode テキスト
s = "こんにちは"  # 日本語の文字列

# bytes はバイト列。b'' リテラルで書ける
b = b"ABC"  # ASCII 範囲のバイトはそのまま書ける

# エスケープで任意のバイトを表現
b_utf8_fragment = b"\xe3\x81\x82"  # UTF-8 で「あ」に相当するバイト断片

# bytearray は変更可能なバイト配列
ba = bytearray(b"ABC")
ba[0] = 0x61  # 'A'(0x41) を 'a'(0x61) に変更

print(type(s), type(b), type(ba))
print(ba)
実行結果
<class 'str'> <class 'bytes'> <class 'bytearray'>
bytearray(b'aBC')

len()の違い(文字数とバイト数)

len()strでは文字の数bytesではバイト数を返します。

文字とバイトで結果が異なる点に注意します。

Python
s = "あ"  # 1 文字
print(len(s))  # 文字数

b_utf8 = s.encode("utf-8")   # UTF-8 では「あ」は 3 バイト
b_cp932 = s.encode("cp932")  # CP932(Shift_JIS) では 2 バイト

print(len(b_utf8))  # バイト数
print(len(b_cp932))  # バイト数
実行結果
1
3
2

絵文字や結合文字では、見た目1文字でもUnicode的には複数コードポイントで構成される場合があり、len(str)が2以上になることがあります。

エンコーディングとデコードの基礎(encode/decode)

文字→バイトに変換する(encode, UTF-8)

エンコード(encoding)strbytesに変換する操作です。

もっとも一般的なのはUTF-8です。

Python
text = "Pythonで文字化けを防ぐ"
data = text.encode("utf-8")  # 文字 → バイト
print(type(data), data[:10])  # 先頭10バイトだけ確認
実行結果
<class 'bytes'> b'Python\xe3\x81'

バイト→文字に変換する(decode, UTF-8)

デコード(decoding)bytesstrに戻す操作です。

エンコードに使ったのと同じ文字コードを指定します。

Python
data = b'Python\xe3\x81\xa7\xe6\x96\x87\xe5\xad\x97'
text = data.decode("utf-8")  # バイト → 文字
print(type(text), text)
実行結果
<class 'str'> Pythonで文字

エンコードとデコードで文字コードが一致しないとエラーや文字化けが発生します。

よく使うエンコーディング(UTF-8, CP932, Shift_JIS)

以下は主な文字コードの概要です。

エンコーディング概要主な用途/備考
UTF-8世界中の文字を扱えるUnicodeの可変長方式現在の標準。Web/クロスプラットフォームに最適
CP932Windows日本語の実装。いわゆるShift_JIS互換Windowsコンソール/レガシーファイルに登場
Shift_JIS旧来の日本語文字コード新規採用は非推奨。レガシーデータで遭遇

新規作成は基本UTF-8、過去データや外部仕様でCP932/Shift_JISを扱うことがあります。

エラー処理(errors=strict/ignore/replace/backslashreplace)

変換時に表せない文字に遭遇すると、デフォルトのerrors="strict"では例外が出ます。

他のモードで挙動を制御できます。

Python
# CP932 に存在しない文字の例: "𝄞"(U+1D11E, 楽譜のト音記号)
s = "音符𝄞"

# strict: 例外
try:
    s.encode("cp932", errors="strict")
except UnicodeEncodeError as e:
    print("strict:", e)

# ignore: 失われる(消える)
print("ignore:", s.encode("cp932", errors="ignore"))

# replace: 置換(通常 '?')
print("replace:", s.encode("cp932", errors="replace"))

# backslashreplace: \uXXXX などのエスケープに
print("backslashreplace:", s.encode("cp932", errors="backslashreplace"))
実行結果
strict: 'cp932' codec can't encode character '\U0001d11e' in position 2: illegal multibyte sequence
ignore: b'\x89\xb9\x95\x84'
replace: b'\x89\xb9\x95\x84?'
backslashreplace: b'\x89\xb9\x95\x84\\U0001d11e'

文字化けの原因(エンコーディング不一致)

エンコードに使った文字コードと、デコードに使う文字コードが一致しない場合に文字化けや例外が起きます。

Python
s = "日本語"
b_cp932 = s.encode("cp932")     # CP932 でエンコード
# 間違って別の文字コードでデコードすると…
try:
    print(b_cp932.decode("utf-8"))  # 多くの場合はエラー
except UnicodeDecodeError as e:
    print("UTF-8 ではデコードできません:", e)

# 無理やり latin-1 でデコードすると、エラーは出ないが文字化けした表示になる
print("latin-1 で無理やり読むと:", b_cp932.decode("latin-1"))
実行結果
UTF-8 ではデコードできません: 'utf-8' codec can't decode byte 0x93 in position 0: invalid start byte
latin-1 で無理やり読むと: “î—€

「エラーが出ない=正しい」ではありません

意味のあるテキストに戻すには正しいエンコーディングの把握が不可欠です。

実践での使い分け(ファイル/ネットワーク)

ファイル読み書きはencodingを指定(open, encoding)

テキストファイルは必ずencoding=...を指定します。

新規はUTF-8が無難です。

Python
# UTF-8 でファイルに書く
text = "こんにちは、世界"
with open("hello.txt", "w", encoding="utf-8") as f:
    f.write(text)

# UTF-8 で読み戻す
with open("hello.txt", "r", encoding="utf-8") as f:
    loaded = f.read()

print(loaded)
実行結果
こんにちは、世界

外部仕様がCP932ならencoding="cp932"を使います。

テキストモードとバイナリモード(r/wとrb/wb)

open()にはテキストモード("r"/"w")とバイナリモード("rb"/"wb")があります。

  • テキストモード: strで読み書き。encodingが使われる。
  • バイナリモード: bytesで読み書き。エンコーディングは関与しない。
Python
# バイナリモードで任意バイトを書き込み
data = bytes([0x41, 0x42, 0x43])  # b"ABC"
with open("out.bin", "wb") as f:
    f.write(data)

# 読み戻すと bytes が得られる
with open("out.bin", "rb") as f:
    raw = f.read()

print(raw, type(raw))
実行結果
b'ABC' <class 'bytes'>

ソケット通信はbytesを送受信(send/recv)

ソケットはバイト列でやり取りします。

テキストプロトコルなら送信前にencode()、受信後にdecode()します。

Python
import socket

# HTTP でヘッダは ASCII 範囲のみ想定。安全のため ASCII でエンコード
with socket.create_connection(("example.com", 80), timeout=5) as sock:
    request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
    sock.sendall(request.encode("ascii"))  # str → bytes
    chunk = sock.recv(100)  # bytes が返る
    print(type(chunk), len(chunk))
実行結果
<class 'bytes'> 100

標準入出力のエンコーディングと環境依存

標準入出力の文字コードは環境依存です。

対話環境やOSにより異なるため、確認しておくと安全です。

Python
import sys, locale

print("stdout encoding:", sys.stdout.encoding)
print("preferred encoding:", locale.getpreferredencoding(False))
実行結果
stdout encoding: utf-8
preferred encoding: cp932

Windowsの古いコンソールではcp932になる場合があります。

必要に応じてPYTHONIOENCODING=utf-8を設定したり、端末のコードページをUTF-8に変更します。

よくあるエラーと対処法

strとbytesを混ぜてTypeErrorになる

strとbytesは直接結合できません

必ずどちらかに揃えます。

Python
try:
    result = b"Hello, " + "世界"  # 型が違う
except TypeError as e:
    print("TypeError:", e)

# 正しい方法: str 同士で連結し、最後に encode する
text = "Hello, " + "世界"
data = text.encode("utf-8")
print(type(data), data)
実行結果
TypeError: can't concat str to bytes
<class 'bytes'> b'Hello, \xe4\xb8\x96\xe7\x95\x8c'

ダブルエンコード/デコードを避ける

Python 3では、str.decode()はありません。

bytes.encode()も基本的に不要です。

「文字にencode」「バイトにdecode」だけが正しい対応です。

Python
s = "あ"
b = s.encode("utf-8")  # OK: str → bytes

# 間違い1: str に decode は存在しない
try:
    s.decode("utf-8")
except AttributeError as e:
    print("誤用(1):", e)

# 間違い2: bytes に encode は不要(多くの場面で誤用の合図)
try:
    b2 = b.encode("utf-8")  # Python の実装では AttributeError になることがあります
except AttributeError as e:
    print("誤用(2):", e)

# 正しい往復
s2 = b.decode("utf-8")  # OK: bytes → str
print(s2)
実行結果
誤用(1): 'str' object has no attribute 'decode'
誤用(2): 'bytes' object has no attribute 'encode'
あ

加えて、すでにUTF-8のbytesを得ているのに再度utf-8でエンコードしようとすると誤りです。

バイト列はそのまま扱うか、文字として処理したい場合のみdecode()します。

BOM付きUTF-8を扱う(utf-8-sig)

一部のツールやExcel向けにBOM(Byte Order Mark)付きUTF-8が必要な場合があります。

Pythonではutf-8-sigを使うとBOMを自動付与/除去できます。

Python
text = "先頭にBOMを付けたい"

# 書き込み時に BOM を付与
with open("bom.txt", "w", encoding="utf-8-sig") as f:
    f.write(text)

# 読み込み時、utf-8 だと先頭に不可視文字(\ufeff)が残る場合がある
with open("bom.txt", "r", encoding="utf-8") as f:
    raw = f.read()
print("utf-8 で読むと:", repr(raw))  # '\ufeff' が見える

# utf-8-sig なら BOM を除去してくれる
with open("bom.txt", "r", encoding="utf-8-sig") as f:
    clean = f.read()
print("utf-8-sig で読むと:", repr(clean))
実行結果
utf-8 で読むと: '\ufeff先頭にBOMを付けたい'
utf-8-sig で読むと: '先頭にBOMを付けたい'

Windowsのファイルパスとエンコーディング注意

WindowsではファイルシステムAPIはUnicode対応なので、strのパス(日本語名を含む)はそのまま扱えます。

一方で、コンソール入出力は環境によりcp932などになるため、表示が文字化けすることがあります

その場合は以下を検討します。

  • 端末のコードページをUTF-8に変更する(chcp 65001)、もしくはWindows Terminal/VS Codeターミナルを使う。
  • 環境変数PYTHONIOENCODING=utf-8を設定する。
  • ファイルの読み書きではencoding="utf-8"を明示する。
  • パス操作はpathlib.Pathを使い、strで一貫して扱う。
Python
from pathlib import Path

p = Path("日本語ファイル.txt")
p.write_text("内容です", encoding="utf-8")
print(p.read_text(encoding="utf-8"))
実行結果
内容です

まとめ

PythonのstrはUnicodeの文字、bytesは生のバイト列であり、両者は明確に別物です。

テキストを保存・送受信する際にはエンコード(文字→バイト)デコード(バイト→文字)を正しく対応付け、encodingを明示することで文字化けを防げます。

特にUTF-8を基本とし、レガシー環境ではCP932/Shift_JISに合わせる、エラー時にはerrorsパラメータを活用する、といった実践的な方針が有効です。

ファイルはopen(..., encoding=...)、ネットワークはbytes中心、標準入出力は環境依存という前提を押さえ、「文字とバイトを混同しない」ことを常に意識してコーディングしてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!