環境
- Linux
- Python 2.7.8 & IPython
- Spark 2.1.0
Pythonのソースコードに日本語使うときって
例えばこんな感じで1行目に文字エンコーティング(文字コード)を宣言するんでしたよね。
(Shebangを1行目に書く場合は、2行目でエンコーディングを宣言)
# -*- coding: utf-8 -*-
# 文字列を代入する
x = "こんにちは"
(参考: https://qiita.com/ronin_gw/items/2c82b727461b18991eff)
この宣言をしないと、実行時にこんな感じで怒られてしまいます。
# -*- coding: utf-8 -*-
# 文字列を代入する
x = "こんにちは"
$ python sample_NG.py
File "sample_NG.py", line 1
SyntaxError: Non-ASCII character '\xe6' in file sample_NG.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
ここまでは、普通のPythonの話です。
これをPySparkでやったところ
実行時エラーが起こった時に、やらかしたエラーの内容が出ずに上のようなエンコーディングエラーが出てしまいます。エンコーディング宣言したのに。
こんなエラーを見せられたところで、何が悪いか全くわかりません。
# -*- coding: utf-8 -*-
import pyspark
def main():
spark = pyspark.sql.SparkSession.builder.getOrCreate()
# なんかやらかした
x = float("abc")
spark.stop()
if __name__ == "__main__":
main()
$ spark-submit pyspark-app.py
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/usr/lib/zookeeper/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
(中略...)
Traceback (most recent call last):
File "/usr/local/bin/ipython", line 11, in <module>
sys.exit(start_ipython())
File "/usr/local/lib/python2.7/site-packages/IPython/__init__.py", line 119, in start_ipython
return launch_new_instance(argv=argv, **kwargs)
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 657, in launch_instance
app.initialize(argv)
File "<decorator-gen-109>", line 2, in initialize
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 87, in catch_config_error
return method(app, *args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/IPython/terminal/ipapp.py", line 315, in initialize
self.init_code()
File "/usr/local/lib/python2.7/site-packages/IPython/core/shellapp.py", line 273, in init_code
self._run_cmd_line_code()
File "/usr/local/lib/python2.7/site-packages/IPython/core/shellapp.py", line 396, in _run_cmd_line_code
self.shell.showtraceback(tb_offset=4)
File "/usr/local/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 1826, in showtraceback
self._showtraceback(etype, value, stb)
File "/usr/local/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 1844, in _showtraceback
print(self.InteractiveTB.stb2text(stb))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 507-514: ordinal not in range(128)
If you suspect this is an IPython bug, please report it at:
https://github.com/ipython/ipython/issues
or send an email to the mailing list at ipython-dev@scipy.org
(以下略...)
原因
PYSPARK_DRIVER_PYTHON
にipython
を指定していると、IPythonがエンコーディング宣言を認識できずにエラーになるみたいです。
IPythonの方が対話的にいろいろ試したい時に便利ですし、エラー表示もわかりやすいのですが…。
以下のようにPYSPARK_DRIVER_PYTHON
がただのpython
なら、普通にエラーの内容が見えます。
$ PYSPARK_DRIVER_PYTHON=python spark-submit pyspark-app.py
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/usr/lib/zookeeper/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
(中略...)
Traceback (most recent call last):
File "/home/foo/pyspark-app.py", line 15, in <module>
main()
File "/home/foo/pyspark-app.py", line 10, in main
x = float("abc")
ValueError: invalid literal for float(): abc
(以下略...)
なぜIPythonでだけハマるかは、あとでわかります。
対策
~/.local/lib/python-2.7/site-packages/usercustomize.py
というファイルを作成し
import sys
sys.setdefaultencoding('utf-8')
と書いて保存しましょう。
フォルダやファイルが存在しなければ、作成してください。
(参考: https://qiita.com/tukiyo3/items/06c0821e5002eb73d43f)
これにより、IPython経由で実行したときでもエラー箇所がわかるようになります。
ValueErrorTraceback (most recent call last)
/home/foo/pyspark-app.py in <module>()
13
14 if __name__ == "__main__":
---> 15 main()
/home/foo/pyspark-app.py in main()
8
9 # なんかやらかした
---> 10 x = float("abc")
11
12 spark.stop()
ValueError: could not convert string to float: abc
別の方法として、環境変数PYTHONIOENCODING
を指定して実行してもOKです。
$ PYTHONIOENCODING=utf-8 spark-submit pyspark-app.py
(参考: http://methane.hatenablog.jp/entry/20120806/1344269400, https://qiita.com/FGtatsuro/items/cf178bc44ce7b068d233)
IPythonはエラー周辺のコードを表示してくれますが、これがマルチバイト文字を含んでいるので、エラー文字列出力処理でエラーになる、という厄介な状態になってしまっていたわけですね。
ついでに
PySparkはエンコーディング宣言を認識してくれないかもしれませんが、Emacsなどのエディタがエンコーディングの自動判別に使ってくれるので、個人的には常に書いておくことをお勧めしたいです。