LoginSignup
223
202

More than 5 years have passed since last update.

Python3で文字列を処理する際の心掛け

Last updated at Posted at 2014-11-27

昨日の記事に引き続き、今度はPython3で文字列を扱う場合の自分なりの方針をまとめてみる。

個人的結論

  • ほとんどの場合は文字列を扱い、標準入出力とも文字列でやり取りする。
  • ただし外部プログラムからバイト列が渡される、などバイト列を扱わなければいけないこともありうる。逆に言うと、そういう場合を除いてはバイト列を扱わない。

これが良い悪いということではなく、現状自分の書くコードではバイト列を扱わなければならないケースが少ないというだけ。

バイト列と文字列

バイト列は特定のエンコード方式でエンコードされており、リテラルではb'a'のように表現する。一方、文字列はUnicodeのコードポイントを並べたものであり、リテラルでは'あいう'のように表現する。

さらっと書いたが、この時点でPython2との扱いの違いが分かる。

  • 「Python3のバイト列」は「Python2のバイト文字列」と扱いが似ている。しかし「Python2のバイト文字列」は『文字列』であるが、「Python3のバイト列」は『文字列』ではない全く別の型である。
  • 「Python3の文字列」と「Python2のユニコード文字列」は同等と考えてよい。リテラル表記に違いがあり、uを文字列の前につけなくてはならなかった「Python2のユニコード文字列」に対して「Python3の文字列」はそれが不要。
(py3.4)~ » ipython
   (省略)
>>> b'a' # バイト列
Out[1]: b'a'

# 非ASCII文字を含む場合、リテラル表記が使えない
# 文字列を特定のエンコード方式でエンコードする必要がある
>>> b'あ' 
  File "<ipython-input-2-c12eb8e58bcd>", line 1
    b'あ'
        ^
SyntaxError: bytes can only contain ASCII literal characters.

>>> 'あ'.encode('utf-8') # 文字列->バイト列(エンコード)
Out[3]: b'\xe3\x81\x82'


>>> 'あ' # 文字列
Out[4]: 'あ'

>>> b'\xe3\x81\x82'.decode('utf-8') # バイト列->文字列(デコード)
Out[5]: 'あ'


# Python2(再掲)
(py2.7)~ » ipython
   (省略)
>>> 'あ' # バイト文字列
Out[1]: '\xe3\x81\x82'

>>> u'あ' # ユニコード文字列
Out[2]: u'\u3042'

>>> 'あ'.decode('utf-8') (or unicode('あ', 'utf-8')) # バイト文字列->ユニコード文字列(=デコード)
Out[3]: u'\u3042'

>>> u'あ'.encode('utf-8') # ユニコード文字列->バイト文字列(=エンコード)
Out[4]: '\xe3\x81\x82'

type関数で確認すると、バイト列はbytes型/文字列はstr型であることが分かる。

>>> type(b'a')
Out[6]: bytes # ≒ Python2のstr型

>>> type('a')
Out[7]: str # ≒ Python2のunicode型

また前述の通り、Python3のバイト列は「文字列」ではない。そのため文字列と連結不可能であるし、サポートしているメソッドも異なる。
この点は割と重要で、Python2だと同じ文字列なのでなんとなく処理が進んでしまい最終的に「UnicodeEncodeErrorががが」となる所が、Python3だと「型が異なることによるエラー」となり、エラー出力/発生箇所が比較的分かりやすい。

>>> s = 'str' # 文字列

>>> b = b'byte' # バイト列

>>> s + b # 文字列 + バイト列はエラー
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-5fe2240a1b50> in <module>()
----> 1 s + b

TypeError: Can't convert 'bytes' object to str implicitly

>>> s.find('t') # 文字列はfindメソッドをサポートしている
Out[11]: 1

>>> b.find('y') # バイト列はfindメソッドをサポートしていない。
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-e1b070a5aaba> in <module>()
----> 1 b.find('y')

TypeError: Type str doesn't support the buffer API

またPython3.2から、標準出力がターミナル以外に接続している時にもlocaleの値から適切なエンコード方式を選択するようだ。そのため、Python2ではUnicodeEncodeErrorが出た以下のケースも正常に動作する。

(ref.) http://methane.hatenablog.jp/entry/20120806/1344269400 の追記

(py3.4)~ » cat test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print('あ' + 'いう')


# ターミナルで実行 (標準入出力をターミナルに接続している)
(py3.4)~ » python test.py
あいう

# ファイルにリダイレクト (標準入出力をターミナル以外に接続している)
(py3.4)~ » python test.py > test.txt
(py3.4)~ » cat test.txt
あいう

もうUnicodeEncodeErrorも恐くない

とか露骨に死亡フラグを立てなくても、UnicodeEncodeErrorに遭遇する可能性は未だある。
例えばcronから実行する場合は、localeからエンコード方式を選択できずにASCIIでエンコード/デコードを試み、大抵UnicodeEncodeErrorを拝む羽目になる。

(ref.) http://www.python.jp/pipermail/python-ml-jp/2014-November/011311.html
(投稿がものすごくタイムリー)

こういったことを考えると、localeを当てにせず常に環境変数PYTHONIOENCODINGでエンコード方式を指定するというのが良いかもしれない。

(ref.) http://methane.hatenablog.jp/entry/20120806/1344269400
(ref.) http://www.python.jp/pipermail/python-ml-jp/2014-November/011314.html

ではバイト列を扱う場合はどうすれば良いか

sys.stdin.buffer(標準入力)/sys.stdout.buffer(標準出力)を使えば、文字列ではなくバイト列を扱える。

(py3.4)~ » cat test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

#print('あ' + 'いう') # printはsys.stdoutに書き込む
sys.stdout.write('あ' + 'いう' + '\n') # sys.stdoutに文字列を書き込む
sys.stdout.buffer.write(('あ' + 'いう' + '\n').encode('utf-8')) # sys.stdout.bufferにバイト列を書き込む

# ターミナルで実行
(py3.4)~ » python test.py
あいう
あいう

# ファイルにリダイレクト
(py3.4)~ » python test.py > test.txt
(py3.4)~ » cat test.txt
あいう
あいう

繰り返しになるが、Python3ではバイト列と文字列は全く別のものである。そのため、文字列を書き込むsys.stdoutにはバイト列は書き込めず、バイト列を書き込むsys.stdout.bufferには文字列は書き込めない。

>>> import sys

# テキストストリーム (ref. https://docs.python.org/3/library/io.html#io.TextIOWrapper)
>>> type(sys.stdout) 
Out[2]: _io.TextIOWrapper

# バイトストリーム (ref. https://docs.python.org/3/library/io.html#io.BufferedWriter)
>>> type(sys.stdout.buffer)
Out[3]: _io.BufferedWriter 

# テキストストリームにバイト列は書き込めない
>>> sys.stdout.write('a'.encode('utf-8')) 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-581ae8b6af82> in <module>()
----> 1 sys.stdout.write('a'.encode('utf-8'))

TypeError: must be str, not bytes

# バイトストリームに文字列は書き込めない
>>> sys.stdout.buffer.write('a') 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-42da1d141b96> in <module>()
----> 1 sys.stdout.buffer.write('a')

TypeError: 'str' does not support the buffer interface

223
202
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
223
202