[Python] UnicodeEncodeErrorが発生した場合は、sitecustomize.pyでデフォルトのエンコーディングを指定する。

環境

この記事の内容は、Ubuntu 6.10, Python 2.4で確認しました。

現象

ユニコード文字列をstr関数に与えると、UnicodeEncodeErrorが発生します。

$ python
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02) 
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> s = u'ほげ'
>>> str(s)
Traceback (most recent call last):
  File "", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

解決方法

以下の/usr/lib/python2.4/site-package/sitecustomize.pyを作成してください。

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

詳細

デフォルトのエンコーディングは、以下のようにして確認できます。

$ python
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'ascii'

sitecustomize.pyは、pythonが起動したときに読み込まれるモジュールです。この中で、python全体の設定を行うことができます。デフォルトのエンコーディングを正しく設定した場合、問題の現象は以下のようになります。

$ python
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02) 
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> s = u'ほげ'
>>> str(s)
'\xe3\x81\xbb\xe3\x81\x92'

なお、sitecustomize.py以外でsys.setdefaultencodingを実行しても、AttributeErrorになります。

$ python
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.setdefaultencoding('utf-8')
Traceback (most recent call last):
  File "", line 1, in ?
  AttributeError: 'module' object has no attribute 'setdefaultencoding'

これは、pythonは起動が終了したら、sysモジュールからsetdefaultencoding関数を削除するためです。

ここで、pythonの起動時の処理を、少し詳しく説明します。pythonは起動時に、モジュールの検索パスを設定し、siteモジュールを読み込みます。Ubuntuなら、/usr/lib/python2.4/site.pyになります。sitecustomizeモジュールも、このsite.pyから読み込まれています。

def execsitecustomize():
    """Run custom site specific code, if available."""
    try:
        import sitecustomize
    except ImportError:
        pass

siteモジュールが読み込まれると、その中のmain関数が呼び出され、main関数の最後でsys.setdefaultencoding関数を削除しています。

def main():
    abs__file__()
    paths_in_sys = removeduppaths()
    if (os.name == "posix" and sys.path and
        os.path.basename(sys.path[-1]) == "Modules"):
        addbuilddir()
    paths_in_sys = addsitepackages(paths_in_sys)
    if sys.platform == 'os2emx':
        setBEGINLIBPATH()
    setquit()
    setcopyright()
    sethelper()
    aliasmbcs()
    setencoding()
    execsitecustomize()
    # Remove sys.setdefaultencoding() so that users cannot change the
    # encoding after initialization.  The test for presence is needed when
    # this module is run as a script, because this code is executed twice.
    if hasattr(sys, "setdefaultencoding"):
        del sys.setdefaultencoding

main()

これにより、sys.setdefaultencodingは、sitecustomize.py内でしか実行できなくなります。

なお、pythonにはsite.pyの読み込みを抑制する-Sオプションがあります。

$ python -h
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
 -c cmd : program passed in as string (terminates option list)
 -d     : debug output from parser (also PYTHONDEBUG=x)
 -E     : ignore environment variables (such as PYTHONPATH)
 -h     : print this help message and exit
 -i     : inspect interactively after running script, (also PYTHONINSPECT=x)
         and force prompts, even if stdin does not appear to be a terminal
 -m mod : run library module as a script (terminates option list)
 -O     : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x)
 -OO    : remove doc-strings in addition to the -O optimizations
 -Q arg : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew
 -S     : don't imply 'import site' on initialization
 -t     : issue warnings about inconsistent tab usage (-tt: issue errors)
 -u     : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x)
         see man page for details on internal buffering relating to '-u'
 -v     : verbose (trace import statements) (also PYTHONVERBOSE=x)
 -V     : print the Python version number and exit
 -W arg : warning control (arg is action:message:category:module:lineno)
 -x     : skip first line of source, allowing use of non-Unix forms of #!cmd
file   : program read from script file
 -      : program read from stdin (default; interactive mode if a tty)
arg ...: arguments passed to program in sys.argv[1:]
Other environment variables:
PYTHONSTARTUP: file executed on interactive startup (no default)
PYTHONPATH   : ':'-separated list of directories prefixed to the
               default module search path.  The result is sys.path.
PYTHONHOME   : alternate  directory (or :).
               The default module search path uses /pythonX.X.
PYTHONCASEOK : ignore case in 'import' statements (Windows).

このオプションを使用すると、site.py以外でもsys.setdefaultencoding関数を使用することができます。

$ python -S
Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)] on linux2
>>> import sys
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__', '__stdin__', '__stdout__', '_getframe', 'api_version', 'argv', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'exc_clear', 'exc_info', 'exc_type', 'excepthook', 'exec_prefix', 'executable', 'exit', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding', 'getrecursionlimit', 'getrefcount', 'hexversion', 'maxint', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdefaultencoding', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'version', 'version_info', 'warnoptions']
>>> sys.setdefaultencoding('utf-8')