文字コードの変換まとめ
※「【Python】日本語を使う」のページにも、文字コードのencode/decodeについて同様の掲載しています。
文字コードとencode/decode
英数字は1文字を1バイトのデータ量で表すことができるが、1バイトのデータ量で表現できる文字の数は限られている。東アジアの言語のように、文字によっては2バイト以上のデータ量で表さなければならないのだが、そのような文字を「マルチバイト文字」と呼んでいる。
1バイトで表現できる情報の数は256種類であり、アルファベットと数字だけであれば256通りもあれば十分であるが、アルファベット以外の文字を表現するには圧倒的に不足している。そこで2バイト以上の表現が必要になるのである。マルチバイト文字というのは、例えば日本語圏であれば、ひらがなやカタカナ、漢字が相当する。
※マルチバイト文字に対して、1バイトで表現可能な文字を「シングルバイト文字」と言う。
文字を表すバイト列は文字コードによって異なる。文字列をバイト列に変換するには encode関数 を用いる。例えば「あいうえお」をバイト列に変換すると次のようになる。
string = "あいうえお"
print(string.encode('utf-8'))
# b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a'
print(string.encode('shift-jis'))
# b'\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8'
print(string.encode('EUC-JP'))
# b'\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa
ここではUTF-8、Shift-JIS、EUC-JPの3種類の文字コードでバイト列に変換している。
反対にバイト列を文字列に直すには decode関数 を用いる。
string1 = "あいうえお".encode('shift-jis')
print(string1.decode('shift-jis'))
# あいうえお
string2 = "あいうえお".encode('EUC-JP')
print(string2.decode('shift-jis'))
# 、「、、、ヲ、ィ、ェ
前者はShift-JIS方式でエンコードしたバイト列をShift-JIS方式で文字列にデコードしているので正しく「あいうえお」と表示されている。
一方で後者はEUC-JP方式でエンコードしたバイト列をShift-JIS方式で無理やりデコードしているので正しくない表示になってしまっている。これがいわゆる「文字化け」である。
各文字に対応するバイト列は文字コードによって違うので、異なる文字コードで展開された場合は正しく文字列に直せない。このように文字化けは、文字コードAが定めるバイト列を文字コードBのバイト列として翻訳してしまうことによって発生するのである。
※encodeとdecodeがごっちゃになる人は、encodeの方を “en-” =「にする」、”code” =「記号、暗号」の組み合わせで「暗号化する」と覚えておくと良い。decodeは “de-” =「解除する」で「暗号を解く」=「平文に直す」と覚えよう。
プログラムは基本的に英数字と記号類で記述するが、時折コードの中で日本語を利用しなければならない場面が出てくる。文字コードを正しく把握していないと、プログラムがテキストファイルを正しく読み書きできなかったり、スクレイピングなどで取得したテキストが文字化けしたりする可能性がある。
Python2系では標準の文字コードが「ASCII」だったため、日本語を扱う際は「# coding: utf-8」などと文字コードを宣言する必要があった。Python3系では標準の文字コードが「UTF-8」なので文字コードの宣言は基本的に不要である。Python3系ユーザーの場合、UTF-8を使っていれば基本的に日本語の表示や読み書きで困ることは無いだろう。
※ASCIIとUTF-8の違いは次の通り。
ASCII:1文字を1バイトで表現する最も基本的なエンコーディング方式
UTF-8:ASCIIに世界中の文字を追加するためのエンコーディング方式
※参考にならないかもしれない参考文献:Unicode一覧表、UTF-8コード表
文字列⇔アスキーコードの変換
文字列をアスキー(ASCII)コードに変換するには以下のようにする。
def string_to_ascii(s):
# ord関数を使用して、文字列の1文字ずつのアスキーコードを取得
ascii_codes = [ord(ch) for ch in s]
return ascii_codes
s = "hello"
ascii_codes = string_to_ascii(s)
print(ascii_codes)
# [104, 101, 108, 108, 111]
反対に、アスキーコードを文字列に変換するには以下のようにする。
def ascii_to_string(ascii_codes):
# chr関数を使用して、アスキーコードから文字列を生成
s = ''.join([chr(code) for code in ascii_codes])
return s
ascii_codes = [104, 101, 108, 108, 111]
s = ascii_to_string(ascii_codes)
print(s)
# "hello"
なお、Python3系においてコードを十進法表記している限りはユニコードでも同様に動作する。16進数(Unicodeエスケープシーケンス)が必要な場合はhex関数を利用する。「平仮名カタカナのASCIIコードとUnicode一覧」のページも参照されたい。
シーザー暗号を作成する
文字コードは文字列を整数に変換するものであるから、文字列をシーザー暗号に変換するプログラムが簡単に書ける。例えば、以下のコードでは入力された平文が3文字後にずらして暗号化される。
def caesar_encrypt(plaintext, shift):
ciphertext = ""
for ch in plaintext:
if ch.isalpha():
# 大文字を小文字に統一する
ch = ch.lower()
# 文字をずらす
ch = chr((ord(ch) - ord('a') + shift) % 26 + ord('a'))
ciphertext += ch
return ciphertext
plaintext= "science"
shift = 3
ciphertext = caesar_encrypt(plaintext, shift)
print(f"Plaintext: {plaintext}") # Plaintext: science
print(f"Ciphertext: {ciphertext}") # Ciphertext: vflhqfh
復号する(平文に戻す)場合は次のような関数を定義すればよい。
def caesar_decrypt(ciphertext, shift):
plaintext = ""
for ch in ciphertext:
if ch.isalpha():
# 大文字を小文字に統一する
ch = ch.lower()
# 文字をずらす
ch = chr((ord(ch) - ord('a') - shift) % 26 + ord('a'))
plaintext += ch
return plaintext
ciphertext = "vflhqfh"
shift = 3
plaintext = caesar_decrypt(ciphertext, shift)
print(f"Ciphertext: {ciphertext}") # Ciphertext: vflhqfh
print(f"Plaintext: {plaintext}") # Plaintext: science
これで任意の英字文字列をシーザー暗号化&復号化することができる。日本語の場合はこれに比べると難しいが、上手く実装すれば同様に可能である。
