Hatena::ブログ(Diary)

かせきのうさぎさん

2015-05-05

time.tznameが文字化けしてるんだが

time.tzname の値がおかしい。

# Python 3.4.3 on Win32
import sys
import time
print(time.tzname[0], file=sys.stderr)  # ==> \x93\x8c\x8b\x9e (\x95W\x8f\x80\x8e\x9e)

化けてますねー (わざわざ sys.stderr に出力したのは、例外で落ちないようにするため)。


この類の文字化けPython 2.x 時代に見たことがある文字コードが変換されないまま、内部文字列になってしまっているやつだ。

とりあえず、こうすれば元に戻せるが、

print(time.tzname[0].encode('raw_unicode_escape').decode('shift_jis'))  # ==> 東京 (標準時)

バグが直ったら動かなくなるな。


そもそも、どこでバグっているのか調べると、ロケールに依存した関数 mbstowcs を使っているのを見つけた

それなら import time する前に setlocale すれば化けないはず。

import locale
locale.setlocale(locale.LC_ALL, '')
import time
print(time.tzname[0])  # ==> 東京 (標準時)

ただ、プログラムを実行した時点で既に time モジュールが読み込まれていると効果がないので、早く直して欲しい。

2014-11-22

warnで警告を表示させる

warnings.warn を使うと警告文を表示させることができる。

import warnings
warnings.warn('警告です')
print('hello world')  # プログラムは続行

実行するとstderrに警告文が表示されるものの、プログラムは停止せず、そのまま実行を継続する。

C:\temp\script.py:2: UserWarning: 警告です
  warnings.warn('警告です')
hello world

print関数とやっていることは同じように見えるかもしれないけど、warn は繰り返し実行しても、同じような警告は1回しか表示されないという利点がある。

import warnings

def foo():
    warnings.warn('警告です')
    print('hello world')

foo()
foo()
D:\temp\script.py:7: UserWarning: 警告です
  warnings.warn('警告です')
hello world
hello world

それから warn には stacklevel という引数があって、警告時に表示する場所を関数の呼び出し元に移動させることができる。

import warnings

def foo():
    warnings.warn('この関数を使わないでください', stacklevel=2)
    print('hello world')

for i in range(3):
    foo()
C:\temp\script.py:8: UserWarning: この関数を使わないでください
  foo()
hello world
hello world
hello world

stacklevel=2 としている理由は warn関数の内部から見て2回呼び出しをさかのぼるためである。


warn の第2引数 category にはクラス名を指定して、警告の種類を設定することができる。

warnings.warn('普通の警告', UserWarning)

よく使うクラスとしては DeprecationWarning がある。廃止された関数モジュールを警告する時に使う。

import warnings

def foo():
    warnings.warn('この関数は廃止されました', DeprecationWarning, stacklevel=2)
    print('hello world')

for i in range(3):
    foo()
hello world
hello world
hello world

あれ? 警告が表示されないよ?

どういうことかというと、DeprecationWarning みたいに開発者向けの警告は初めから表示されないようになっているんだなあ。

この動作を変更するにはpythonオプションを変更する。

python -W default script.py
# 以下は上の省略形
python -Wd script.py

毎回オプションをつけるのが面倒な場合は、PYTHONWARNINGS という名前の環境変数を作って default と書いておく方法もある。

設定した状態で普段使っているスクリプトを実行すると思わぬ発見があるかも。自分はこれで廃止予定になっているライブラリを使っていることに何度か気づいたことがある。


表示されない警告の種類はPythonのバージョンによって違う。最新の Python 3.4.2 だとこんな感じ。

import warnings
for i in warnings.filters:
    print(i)
('ignore', None, <class 'DeprecationWarning'>, None, 0)
('ignore', None, <class 'PendingDeprecationWarning'>, None, 0)
('ignore', None, <class 'ImportWarning'>, None, 0)
('ignore', None, <class 'BytesWarning'>, None, 0)
('ignore', None, <class 'ResourceWarning'>, None, 0)

DeprecationWarning の他にも表示されない警告があるね。

こいつらはフィルターと呼ばれていて、警告が発せられると上からマッチするものがないか見ていく。マッチするフィルターがあれば警告を無視(ignore)したりエラー(error)にしたりと所定の操作を実行する。

特定のモジュールやメッセージを限定したフィルターも書けるけど、まぁ普通は使わないと思う。興味があれば参考文献をどうぞ。


参考文献

warnings — Non-fatal alerts - Python Module of the Week
フィルターの書き方や警告の表示方法について
29.5. warnings — 警告の制御 — Python 3.4.2 ドキュメント
warningsの公式ドキュメント

2014-11-10

tkinterでは基本多言語面しか使えない

Python の tkinter.Entry に入力した内容を取り出そうとすると、こういう例外が出る文字がある。

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xed in position 0: invalid continuation byte

何とか迂回できないものかと、対話モードで色々弄っていると、

>>> s = StringVar()
>>> s.set('\U0001F5FF')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\bin\Python34\lib\tkinter\__init__.py", line 263, in set
    return self._tk.globalsetvar(self._name, value)
_tkinter.TclError: character U+1f5ff is above the range (U+0000-U+FFFF) allowed by Tcl
訳:文字 U+1f5ff は Tcl で扱える範囲(U+0000-U+FFFF)を上回ってますねぇ

と言われた。

基本多言語面にしか対応してないのか、Tcl。がっくしだよ。

tkinterよ、できれば通常の使用でまともなエラーメッセージを吐くようにしておいてくれると助かるんだが。