トホホな疑問(12) Python3移行、文字列数値変換にハマる

昨日に続いてPythonネタです。数か月前にPython3へ移行始めた件、何かの投稿の中に書きました。今年いっぱいでPython2.7のサポート終了、来年からはPython3に天下統一、ということで急に不安になったがためですね。ま、もともとそんなに特殊なものは書いていなかった(筈)なので、順調に移行(というか手動移植による両用化)できていると思っていたのです。が、やっぱりね、ハマりました。それもとてもプリミティブなところに。

テキストデータを処理していると、数値を表す文字列を数値に変換する必要ありますね。ホント単純作業なので、何の気もなくやっていたのですが、ここに落とし穴がありました。ご存知の組み込み関数

int(変換すべき文字列, 基数)

というやつ。たまたまなんですが、変換すべき文字列は16進数で、あるレコードのID的なものでした。通常はユニークなIDなのですが、ときどき例外的にIDがダブるような代物だったのです。後でダブった時はダブったなりの処理をしないとならなかったので、16進文字列の状態のときに以下のように加工しておりました(そういう加工が良くないと言えば、そうかも知れません)

  • ユニークな(最初の)IDはそのまま16進文字列
  • ダブった(2回目以降の)IDの場合、IDを示す文字列の後 “_数字” という形でダブッた回数を付加

Python 2.7 で「後で数値に変換処理」するときは、こんな感じになります。

Python 2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> id = "1A"
>>> int(id, 16)
26
>>> id = "1A_1"
>>> int(id, 16)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 16: '1A_1'

つまり、普通のIDは数値に変換できるけれども、ダブった御印をつけてあるものは、ValueError例外を発生する、と。この例外を捕まえてダブった時の処理(まさに例外のための処理)を書いておったのでした。

でもね、残念ながら Python 3.7 ではそうは問屋が卸してくれなかったのです。

Python 3.7.3 (default, Apr 3 2019, 05:39:12)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> id = "1A"
>>> int(id, 16)
26
>>> id = "1A_1"
>>> int(id, 16)
417

分かります? Python 2.7 のときは例外に落ちていたのに、Python 3.7 になったら例外に落ちるどころか、本来のIDとは違う数値に変換されてしまいました。なんでじゃ、といって、Python 3.7 の組み込み関数のマニュアルをちゃんと読んでいれば直ぐ分かる話だったのです。ご本家の組み込み関数の公式ドキュメント(日本語)から引用させていただきましょう。intの説明の後の方に書いてありました。

バージョン 3.6 で変更: コードリテラル中で桁をグループ化するのにアンダースコアを利用できます。

なんと、int() 改良されていました。なんとそれも3.6の時代、大分昔に。変換対象の文字列を見やすくするためにアンダースコア、つまり _ を書き込んでもよくなっていました。おお、これは便利だ。しかしながら、私の上記表現はまさに飛んで火にいる夏の虫的に、この改良を踏みにじり、無用なデバッグの泥沼へと続いていたのでした。

無知は恐ろしい。マニュアル良く読むべし。

こういうプリミティブな文字列数値変換でバグっているとは露にも思わず、ID文字列にある筈の無い数値が混じっていたために起こった混乱の理由を遠く離れた下流で見つけて時間を使ってしまいました。

こんなところ、大丈夫だろ~などという仮初な安心には根拠がない。トホホ。

トホホな疑問(11) Python、空listの繰り返し へ戻る

トホホな疑問(13) Python、lxml、デフォルト名前空間とXPath へ進む