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

# 環境
- Linux
- Python 2.7.8 & IPython
- Spark 2.1.0

# Pythonのソースコードに日本語使うときって
例えばこんな感じで1行目に文字エンコーティング（文字コード）を宣言するんでしたよね。
（Shebangを1行目に書く場合は、2行目でエンコーディングを宣言）

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

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

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

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

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

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

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

```python: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()
```

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

```bash: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経由で実行したときでもエラー箇所がわかるようになります。

```bash: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です。


```bash: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などのエディタがエンコーディングの自動判別に使ってくれるので、個人的には常に書いておくことをお勧めしたいです。