【Python】【Python 3.6】【Encode エラー】(UnicodeEncodeError: 'ascii' codec can't encode characters)と(TypeError: write() argument must be str, not bytes)

いくら調べても全く分からなかった「Encode Error」ですがようやく解決しました。

ただし、まだまだ Python の Encode の構造がよく分かっていません。

 

 

Python 3.6 のエンコード

「Python 2系」「Python 3系」はエンコードの仕様が異なるので注意です。

様々な Python プログラムの記事を掲載しているサイトを閲覧すると「Python 2系」と「Python 3系」が入り混じっています。

しかもまだまだ「Python 2系」のサイトが多いです。

 

Python 3系のエンコードに関してまとめると

  • 「バイト列」と「文字列」は別物
  • Python 3 では「文字列」は Unicode 文字列として扱われる
  • 文字列(str型)→バイト列(bytes型)への変換は「'あ'.encode('utf-8')」
  • バイト列(bytes型)→文字列(str型)への変換は「'\u30cb\u30fc'.decode('utf-8')」
  • Encode 文字列→バイト列
  • Decode バイト列→文字列

 

 

Python でファイルに書き込む場合

一般的なファイルを開いて書き込む方法です。

 

【プログラム例】

(pyenv) [test@SAKURA_VPS scraping]$ cat test7.py
# -*- coding: utf-8 -*-

 

 

f = open('/tmp/text.txt','w')
f.write('ファイルに書き込む文字列\n')

 

(pyenv) [test@SAKURA_VPS scraping]$

 

 

【実行結果】

/tmp/text.txt を開いて内容を確認します。

(pyenv) [test@SAKURA_VPS scraping]$ python test7.py ← プログラムを実行します。

(pyenv) [test@SAKURA_VPS scraping]$ cat /tmp/text.txt
ファイルに書き込む文字列
(pyenv) [test@SAKURA_VPS scraping]$

 

ここまではまったく問題ありません。

しかし、これがスクリプトやOSから実行するプログラムになった場合に思いもよらない「エンコードエラー(Encode Error)」が出力されることになりました。

 

OSからプログラムを実行すると「UnicodeEncodeError: 'ascii' codec can't encode character ~」が出力される

以前メール受信をトリガーとしてプログラムを起動する手順の記事を書きました。

 

【Python】メール受信をトリガーとして【さくらVPS】サーバー上の Python プログラムをスタートしたい

 

メール受信をきっかけに OS からプログラムを実行(つまり手動で Python コマンドを実行しない)したところ、以下のような「UnicodeEncodeError」が出力されました。

Dec 23 10:38:53 xxxxxx postfix/local[10479]: AF75E1E7295D: to=<test@mail.xxx.com>, relay=local, delay=12, delays=0.01/0/0/12, dsn=5.3.0, status=bounced (Command died with status 1: " /home/test/pyenv/scraping/mailget.sh". Command output: Traceback (most recent call last):   File "/home/test/pyenv/scraping/mailget_timestamp.py", line 133, in <module>     tmpf.write(check) UnicodeEncodeError: 'ascii' codec can't encode character '\u3042' in position 0: ordinal not in range(128) )

 

 

 

標準出力をラップしても効果なし

OSから実行の場合は効果なし

import sys, codecs
sys.stdout = codecs.EncodedFile(sys.stdout, 'utf_8')

 

こちらもOSから実行の場合は効果なし

import sys, codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout)

 

 

どうしてもダメだった例

端末(Teraterm など)から Python コマンドを実行する場合は問題なくうまくいくのに、OS からプログラムを実行するとエラーになる例です。

f = open('/tmp/test.txt', 'w')
check = u'あ'.encode('utf-8')

 

f.write(check)

 

 

以下のように「TypeError: write() argument must be str, not bytes」が出力されます。

Dec 23 13:32:41 xxxxxx postfix/local[13150]: F36331E7295D: to=<test@mail.xxxx.com>, relay=local, delay=13, delays=0.02/0.01/0/13, dsn=5.3.0, status=bounced (Command died with status 1: " /home/test/pyenv/scraping/mailget.sh". Command output: Traceback (most recent call last):   File "/home/test/pyenv/scraping/mailget_timestamp.py", line 135, in <module>     f.write(check) TypeError: write() argument must be str, not bytes )

 

 

そのため、「byte」から「str」に変換すればいいのかと考え、以下のようにデコードしました。

f = open('/tmp/test.txt', 'w')
check = u'あ'.encode('utf-8')
check = check.decode('utf-8')

 

f.write(check)

 

 

しかし、実行すると「UnicodeEncodeError: 'ascii' codec can't encode character ~」が出力されます。

Dec 23 13:35:05 xxxxxx postfix/local[13218]: 3A3EB1E7295D: to=<test@mail.xxxx.com>, relay=local, delay=13, delays=0.02/0.01/0/13, dsn=5.3.0, status=bounced (Command died with status 1: " /home/test/pyenv/scraping/mailget.sh". Command output: Traceback (most recent call last):   File "/home/test/pyenv/scraping/mailget_timestamp.py", line 135, in <module>     f.write(check) UnicodeEncodeError: 'ascii' codec can't encode character '\u3042' in position 0: ordinal not in range(128) )

 

ここまで来ると堂々巡りですが、試しに再度エンコードをしてみます。

check = u'あ'.encode('utf-8')
check = check.decode('utf-8')
check = check.encode('utf-8')

 

f.write(check)

 

しかし、やはり「TypeError: write() argument must be str, not bytes」と出力されました。

Dec 23 13:40:16 xxxxxx postfix/local[13346]: 7C8811E7295D: to=<test@mail.xxxx.com>, relay=local, delay=13, delays=0.02/0.01/0/13, dsn=5.3.0, status=bounced (Command died with status 1: " /home/test/pyenv/scraping/mailget.sh". Command output: Traceback (most recent call last):   File "/home/test/pyenv/scraping/mailget_timestamp.py", line 135, in <module>     f.write(check) TypeError: write() argument must be str, not bytes )

 

そもそも何が悪いのか?

write()は一体どうなっているのか?

 

write()に書き込むオブジェクトは「文字列(テキストモード)」か「バイト列(バイナリーモード)」に変換する必要がある

write()で書き込みができるのは

  • 文字列(テキストモード)
  • バイト列(bytes列)(バイナリ―モード)

の2つだけです。

 

ファイル操作のモード

  • t : テキストモード(デフォルト)
  • b : バイナリ―モード

 

【解決策】OSからプログラムを実行する場合は write() でファイルをバイト列(bytes型)でオープンすること

write()を調査したところ、モードが2つあります。

デフォルトは「テキストモード」です。

そもそも「bytes」にしろとメッセージが出力されているのでモードをバイナリモードに設定してみました。 

 

f = open('/tmp/test.txt', 'wb')
check = u'あ'.encode('utf-8') ← uを付けています。

 

f.write(check)

 

 

ファイルを開いて内容を確認します。

問題なく文字「あ」が表示されています。

(pyenv) [test@SAKURA_VPS scraping]$ cat /tmp/test.txt
(pyenv) [test@SAKURA_VPS scraping]$

 

 

次は「u」を外しました。

f = open('/tmp/test.txt', 'wb')
check = 'あ'.encode('utf-8') ← uを外しています。

 

f.write(check)

 

 

ファイルを開いて内容を確認します。

こちらも問題なく文字「あ」が表示されています。

(pyenv) [test@SAKURA_VPS scraping]$ cat /tmp/test.txt
(pyenv) [test@SAKURA_VPS scraping]$

 

 

 

参考にしたサイト

ありがとうございます!

 

 

 

 

Posted by 100%レンタルサーバーを使いこなすサイト管理人