LoginSignup
2
0

More than 5 years have passed since last update.

PySparkのソースコードにマルチバイト文字を使うとハマるかも

Posted at

環境

  • Linux
  • Python 2.7.8 & IPython
  • Spark 2.1.0

Pythonのソースコードに日本語使うときって

例えばこんな感じで1行目に文字エンコーティング(文字コード)を宣言するんでしたよね。
(Shebangを1行目に書く場合は、2行目でエンコーディングを宣言)

sample_OK.py
# -*- coding: utf-8 -*-

# 文字列を代入する
x = "こんにちは"

(参考: https://qiita.com/ronin_gw/items/2c82b727461b18991eff)

この宣言をしないと、実行時にこんな感じで怒られてしまいます。

sample_NG.py
# -*- coding: utf-8 -*-

# 文字列を代入する
x = "こんにちは"
terminal
$ 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でやったところ

実行時エラーが起こった時に、やらかしたエラーの内容が出ずに上のようなエンコーディングエラーが出てしまいます。エンコーディング宣言したのに。
こんなエラーを見せられたところで、何が悪いか全くわかりません。

pyspark-app.py
# -*- coding: utf-8 -*-                                                                                                                        

import pyspark

def main():
    spark = pyspark.sql.SparkSession.builder.getOrCreate()

    # なんかやらかした
    x = float("abc")

    spark.stop()

if __name__ == "__main__":
    main()
terminal
$ 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_PYTHONipythonを指定していると、IPythonがエンコーディング宣言を認識できずにエラーになるみたいです。
IPythonの方が対話的にいろいろ試したい時に便利ですし、エラー表示もわかりやすいのですが…。
以下のようにPYSPARK_DRIVER_PYTHONがただのpythonなら、普通にエラーの内容が見えます。

terminal
$ 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というファイルを作成し

usercustomize.py
import sys
sys.setdefaultencoding('utf-8')

と書いて保存しましょう。
フォルダやファイルが存在しなければ、作成してください。
(参考: https://qiita.com/tukiyo3/items/06c0821e5002eb73d43f)

これにより、IPython経由で実行したときでもエラー箇所がわかるようになります。

terminal
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です。

terminal
$ PYTHONIOENCODING=utf-8 spark-submit pyspark-app.py

(参考: http://methane.hatenablog.jp/entry/20120806/1344269400, https://qiita.com/FGtatsuro/items/cf178bc44ce7b068d233)

IPythonはエラー周辺のコードを表示してくれますが、これがマルチバイト文字を含んでいるので、エラー文字列出力処理でエラーになる、という厄介な状態になってしまっていたわけですね。

ついでに

PySparkはエンコーディング宣言を認識してくれないかもしれませんが、Emacsなどのエディタがエンコーディングの自動判別に使ってくれるので、個人的には常に書いておくことをお勧めしたいです。

2
0
1

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
2
0