Python2とPython3の文字列挙動の違いを知らないことによる古いコードバグ
Pythonをブラウザで実行しながら実践的に学ぶ
Pythonの基礎からソフトウェアアーキテクチャ,アルゴリズムなどの応用的な内容まで幅広く学べます。
ブラウザ上で直接Pythonコードを試すことができ、実践的なスキルを身につけることが可能です。
Pythonを学んでいると「Python2とPython3では文字列の扱いが違う」という話を聞くことがあります。 でも実際には「どのように違うのか?」「その違いがどんな問題を引き起こすのか?」まで説明できる人は意外と少ないのではないでしょうか。
特に古いコードを触る場面では、この知識がないと本当に苦労します。
私自身、エンジニア歴10年の中で、過去にPython2で書かれたシステムをPython3に移行するプロジェクトを経験しました。 そこで最も多かったトラブルの一つが、文字列の扱いの違いによる予期せぬバグでした。
この記事では、Python2とPython3の文字列の違いを初心者にもわかりやすく、実例や失敗談を交えながら徹底的に解説します。 これを理解すれば、古いコードを読むときの不安や、移行時に直面するトラブルを大幅に減らすことができるはずです。
Python2とPython3で文字列はどう変わったのか?¶
まずは全体像を理解しましょう。
Python2とPython3では、文字列の扱いが根本的に異なります。
Python2では「str」と「unicode」という2種類の文字列型が存在していました。 対してPython3では「str」に統一され、すべての文字列がユニコードとして扱われるようになりました。
この違いは、一見小さな変更のように思えますが、実際にはプログラムの挙動に大きな影響を与えます。 文字化け、型エラー、API通信の失敗など、現場で遭遇するトラブルの多くはこの仕様の違いが原因です。
Python2での文字列の扱い¶
Python2での文字列の扱いについて詳しくみていきましょう。
strとunicodeの違い¶
Python2では、ダブルクォートやシングルクォートで囲んだ文字列は「str型」になります。
これはバイト列として扱われます。
一方で、u
を先頭につけた文字列は「unicode型」として扱われ、実際の文字データをユニコードで保持します。
# Python2の例
s1 = "hello" # str型(バイト列)
s2 = u"こんにちは" # unicode型(ユニコード文字列)
print type(s1) # <type 'str'>
print type(s2) # <type 'unicode'>
バグが起こる典型例¶
この2種類の文字列を混ぜて使おうとすると、次のようにエラーになります。
# Python2
s1 = "こんにちは" # 実際にはバイト列
s2 = u"こんばんは" # ユニコード文字列
print s1 + s2
# UnicodeDecodeError が発生することがある
「見た目は同じ日本語の文字列なのに、型が違うために結合できない」というのがPython2時代のやっかいな点でした。
私も当時、新人のころに「なんで足し算できないの?」と何時間も悩んだ経験があります。 これが、後に「Python2の最大の罠」として語り継がれている所以です。
Python3での文字列の扱い¶
続いて、Python3での文字列の扱いについて見ていきましょう。
ユニコードに統一されたstr¶
Python3では、この混乱を解消するために文字列はすべてユニコードという仕様に統一されました。 そのため、通常の文字列リテラルはすべて「str型」となり、日本語も英語も問題なく扱えるようになっています。
# Python3の例
s1 = "こんにちは" # str型(ユニコード)
s2 = "こんばんは" # str型(ユニコード)
print(type(s1)) # <class 'str'>
print(s1 + s2) # 結合も可能
バイト列を明示するbytes型¶
では、バイト列を扱いたいときはどうするのか?
Python3ではb""
を使って「bytes型」として明示的に書きます。
b1 = b"hello" # bytes型
print(type(b1)) # <class 'bytes'>
このように「文字列=ユニコード」「バイト列=bytes」と明確に分かれたことで、Python3は扱いやすくなりました。
違いを表で整理してみる¶
文章だけでは混乱しやすいので、Python2とPython3の違いを表にして整理しましょう。
バージョン | 文字列の型 | u文字列の型 | b文字列の型 |
---|---|---|---|
Python2 | str(バイト列) | unicode(ユニコード) | str(バイト列)※Python2.7以降 |
Python3 | str(ユニコード) | str(ユニコード、uは書いてもOK) | bytes(バイト列) |
この表を見れば、同じように書いたコードでもPython2とPython3ではまったく違う型になることがわかります。 古いコードを移行するときに「同じように見えるのに動かない」原因はここにあります。
実際に起きやすいバグの例¶
ここからは、私が実際に遭遇したトラブルを紹介します。
ファイル処理でのエラー¶
Python2のコードをそのままPython3で実行すると、ファイルから読み込んだデータの型が変わってしまうことがあります。
# Python2
with open("data.txt") as f:
text = f.read() # str(バイト列)
Python3では、同じコードで読み込むとstr(ユニコード)
になります。
これが原因で「bytes」と「str」が混在してしまい、比較や結合でエラーが発生しました。
API通信でのトラブル¶
外部APIに日本語データを送信する際、Python2では文字列をそのまま送れましたが、Python3ではエンコードが必要になりました。
# Python3
import requests
payload = "こんにちは"
# NG: TypeErrorになる場合がある
# requests.post(url, data=payload)
# OK: 明示的にエンコードする
requests.post(url, data=payload.encode("utf-8"))
こうした違いを知らないと、移行後に「通信が通らない」という致命的なバグに繋がります。
初心者が混乱しやすいポイント¶
初心者が一番戸惑うのは「printすると文字が表示されるのに、処理をするとエラーになる」ことです。 これは、見た目は同じに見えるけれど、中身の型が違うせいです。
私もかつて「文字化けかな?」と勘違いして原因を見誤ったことがあります。 実際には「ユニコードかバイト列か」という違いを意識するだけで、解決できる問題でした。
Python3時代に気をつけること¶
「もうPython2は使ってないから関係ない」
と思う方もいるかもしれません。
しかし現場では、いまだにPython2で作られたコードを保守したり、移行したりするケースが残っています。 だからこそ、Python3で新規開発をする際も「文字列はユニコードが基本。バイト列は必要なときだけ明示する」という意識を持つことが大切です。
実務で役立つテクニック¶
ここまで解説してきたように、Python2とPython3では文字列の扱いが大きく異なります。 その違いを理解していても、日常のコーディングで油断すると思わぬところでバグに遭遇してしまうことがあります。
そこで、私が実務の中で実際に気をつけているちょっとした習慣を紹介します。
これを意識するだけで、文字列に関するトラブルの大半は避けられるようになりました。
型を確認する習慣を持つ¶
まず大切なのは文字列の型を常に意識することです。
見た目が同じ文字列でも、str
なのかbytes
なのかで挙動はまったく変わります。
そこで、コードの中で怪しいと感じたら、まずは型を出力して確認する癖をつけましょう。
これだけで「なぜ動かないのか」が一気に見えてきます。
text = "こんにちは"
print(type(text)) # <class 'str'>
上の例のように、type()
でチェックするのは本当に地味ですが、実務では何度も助けられた方法です。
エンコードとデコードを必ず明示する¶
次に重要なのは、エンコードやデコードを必ず明示することです。
Python3では文字列はユニコードが基本ですが、外部システムやAPI、ファイル入出力などではバイト列を扱う場面が頻繁に登場します。
このとき、暗黙の変換に頼ると環境依存のバグにつながるので、必ず自分でencode()
とdecode()
を指定するのが安全です。
text = "こんにちは"
b = text.encode("utf-8") # str -> bytes
s = b.decode("utf-8") # bytes -> str
このように処理を分けて書くことで、「ここで文字列をバイト列に変換しているんだな」「ここで元に戻しているんだな」と、後から読む人もすぐに理解できます。
コードレビューをするときも、encode/decodeが明示されていれば安心して読めますし、意図を正しく共有できます。
まとめ¶
Python2とPython3の文字列の違いは、プログラマーにとってつまずきやすいポイントです。
- Python2では
"文字列"
がバイト列、u"文字列"
がユニコード - Python3では
"文字列"
がユニコード、b"文字列"
がバイト列 - ファイル処理やAPI通信ではこの違いが原因でエラーが発生する
- encode/decodeを明示的に使えば安全に扱える
私の経験上、この知識を持っているかどうかで開発効率は大きく変わります。 古いコードを触るときも、新しいコードを書くときも、ぜひ意識してみてください。
この記事が、あなたが「文字列の違い」で悩む時間を減らすきっかけになれば嬉しいです。
ここまでお読みいただき、ありがとうございました!