Python3での文字列のstr型とbytes型のまとめ

# Python3で文字列を操作してみる #
現在、Pythonで自然言語処理の勉強をしているのですが、その過程でPython3での文字列の取り扱い方を勉強したのでメモとして残しておくことにしました。本当に初歩的な内容なので、普段からPythonを触っている方には参考にならないと思いますが、僕と同じ初心者の方の参考になれればと思います。

また、正確さよりも理解の容易さを優先したのと、僕の未熟さ故に内容が正確でない可能性があります。もし、この記事を読んで目に余る誤りを発見された場合はご連絡していただければと思います。

# 目的・概要 #
Python3での文字列の取り扱い方を勉強したので、その内容をまとめました。この記事を読むと以下の事がわかると思います。

1. Python3では文字列は全てstr型として扱われる
2. str型では文字データをutf-8で扱う
3. どのような文字コードのテキストファイルでもPythonで扱うにはstr型(utf-8)に変換する必要がある。


# Python3では文字列を扱う組み込み型 #
## str型 ##
Python3にはstr型という文字列を扱うための組み込み型が用意されており、文字列は全てstr型の変数(もしくはリテラル)として扱われます。

```python
a = "あい"
print(type(a)) # => <class 'str'>
```

世の中には多くの文字コードがありますが、どのような文字コードのデータでもPython上で文字列として扱う場合には、str型に変換して扱う必要があります。後でstr型が実はutf-8であることがわかります。

## bytes型 ##
Pythonで文字列の取り扱いを理解するのに必要なのがbytes型と呼ばれる組み込み型です。bytes型のデータはバイトデータと呼ばれ、いわゆるバイナリデータのことです。数字にしろ、文字列にしろ、コンピュータ上では全てのデータは0と1のバイナリデータで取り扱われるので、文字列の扱いを理解するのにbytes型は重要になります。

### 文字コード ###
世の中には多くの文字コードが存在していますが、Pythonではstr型の文字列を指定した文字コードのバイトデータに変換することができます。

```python
a = "あい"
print(type(a)) # => <class 'str'>

a_utf8 = a.encode("utf-8")
print(type(a_utf8)) # => <class 'bytes'>
print(a_utf8) # => b'\xe3\x81\x82\xe3\x81\x84'

a_shifjis = a.encode("shift-jis")
print(type(a_shiftjis)) # => <class 'bytes'>
print(a_shiftjis) # => b'\x82\xa0\x82\xa2'
```

ただし、bytes型に変換すると、もはやstr型ではないので文字列として扱うことはできなくなります。また、この時点ではint型やfloat型ですらないので注意が必要です。bytes型のデータをstr型やint型、float型として扱うにはそれぞれの型への変換が必要です。

### ちょっと実験 ###

実験でstr型をbytes型に変換して、さらにint型とstr型に変換してみます。

```python
a = "あい"

a_utf8 = a.encode("utf-8") #utf-8の文字コードのバイナリデータに変換
print(type(a_utf8)) # => <class 'bytes'>
print(a_utf8) # => b'\xe3\x81\x82\xe3\x81\x84'

a_str = a_utf8.decode("utf-8") # バイナリデータをutf-8の文字コードとして解釈してstr型に変換
print(type(a_str)) # => <class 'str'>
print(a_str) # => あい

a_int = int.from_bytes(a_utf8, "little") # バイナリデータをリトルエンディアンで整数化する
print(type(a_int)) # => <class 'int'>
print(a_int) # => 145693402628579
```

Pythonの中での組み込み型の関係のイメージは、byte型が中央にあって、その周りをstr型やint型が存在しbyte型を経由して型を変換することができるというものです。(本当にただのイメージです。自分の簡単な実験だけが根拠のため正しいかどうかは不明です。)


<img alt="IMG_0003.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227731/b9224815-c433-0a9b-47cb-5a3d0d7e314e.jpeg" width="250">

# 外部データの取り込み #
Pythonで外部ファイルからテキストデータを読み込みは、バイナリデータとしてデータを受け取り内部でstr型に変換すればどの文字コードでファイルが作成されていても取り扱えそうです。各文字コードのファイルの作成方法は最後に載せてあります。

ファイルがutf-8の場合は

```text:sample_utf8.txt
あい
```

```python:readutf8.py
with open("sample_utf8.txt", "rb") as fin:
  a = fin.read() # ファイルデータをバイナリデータとして変数に代入
  a = a.decode("utf-8") # バイナリデータをutf-8のデータとしてstr型へ変換
  print(a) # => あい
```

ファイルがShift-JISの場合は

```text:sample_shift.txt
あい # Shift-JISで「あい」と書いてあります。Macなどutf-8の環境では文字が表示されませんでした。
```

```python:readshift.py
with open("sample_shift.txt", "rb") as fin:
  a = fin.read() # ファイルデータをバイナリデータとして変数に代入
  a = a.decode("shift-jis") # バイナリデータをShift-JISのデータとしてstr型へ変換
  print(a) # => あい
```

readutf8.pyを実行すると「あい」と標準出力に出力されます。それでは次にShift-JISのファイルを読み込んでみます。

### 文字コードを指定してファイルを開く ###
先ほどは、一度バイナリデータとしてテキストデータを受け取りstr型に変換する方法でファイルの読み込みを行いましたが、文字コードを指定してファイルを開くこともできます。

```python:readshift2.py
with open("sample_shift.txt", "r", encoding="shift-jis") as fin:
  a = fin.read() # 
  print(a) # => あい
```

ファイルを開く際に文字コードを指定すればstr型に変換されたデータを受け取れます。


# str型はUTF-8 #
Pythonで文字列はstr型として扱われると説明しましたが、実はこのstr型はutf-8でデータを扱っています。実際、str型の文字列をファイルに保存して、そのファイルをバイナリデータとして中身を見るとutf-8で保存されているのが確認できます。

```write_str.py
a = "あい"
with open("sample_str.txt", "w") as fout:
  fout.write(a)
```

`sample_str.txt`のバイナリデータを確認するにはターミナルで以下を実行します。その結果、以下の出力が得られ、str型のデータがutf-8のデータとして保存されたのが確認できます。

```
>>hexdump sample_str.txt
0000000 e3 81 82 e3 81 84   #UTF-8で「あ」は0xe38182、「い」は0xe30184です。
0000006
```

よって、実はUTF-8のテキストファイルは特に文字コードを指定せずに、以下のように直接読み込む事ができます。

```read_str.py
with open("sample_str.txt") as fin:
  a = fin.read()
  print(a) # => あい
```




# 文字コードを指定してテキストをファイルに保存する #
str型の文字列を文字コードを指定してファイルに書き込むためのプログラムは以下のようにしました。


```writeshif.py

a = "あい" # str型
a_utf8 = a.encode("utf-8") # aをutf-8のバイナリデータに変換
a_shift = a.encode("shift-jis") # aをShift-JISのバイナリデータに変換

with open("sample_utf8.txt", "wb") as fout:
  fout.write(a_utf8) # a_utf8をバイナリデータとしてファイルに保存

with open("sample_shift.txt", "wb") as fout:
  fout.write(a_shift) # a_shiftをバイナリデータとしてファイルに保存

```


# まとめ #
Pythonでの文字列の扱われ方を調べて以下の事がわかりました。

1. Python3では文字列は全てstr型として扱われる
2. str型では文字データをutf-8で扱う
3. どのような文字コードのテキストファイルでもPythonで扱うにはstr型(utf-8)に変換する必要がある。

# 参考にしたサイト #

[Pythonのopen関数のencoding引数は必須でもいいんじゃない-はてなブログ takegさん](https://takeg.hatenadiary.jp/entry/2018/02/02/115754)

[初心者向け!Pythonでbytesを扱う方法-TechAcademyマガジン](https://techacademy.jp/magazine/18885)

[python3のbytes型とstr型の比較と変換方法-Python Snippets](https://python.civic-apps.com/python3-bytes-str-convert/)