どうでもいい話(でも一応Pythonのネタ)

「読まなくても人生損しないよ」なタイトル、久々。でも一応技術ネタではある。

ふと、「そういえば Python にはインデクスで split ってないよな?」と思っただけの話、からの。

Python の場合全般に、「シーケンスの扱い」のインターフェイスが綺麗なので、何かが欠けてて困ることはまずないし、Unicode に関係するものを除けば、文字列メソッドの欠けが気になることもほとんどない。midやらrightやらleftやらpadやらの「stringUtilsが必要だ!」と思ってバリバリ書き始める輩はあとを絶たないが、大抵のものは素の Python で全くのストレスなく出来ることを知らないか、「BASICと同じでなければ死ぬ」とか「Java以外は言語ではない」みたいな、自分の知ってる言語流儀以外を認められない可哀相な人々によって作られてしまうものたちだ。

「「シーケンスの扱い」のインターフェイスが綺麗」は、まぁ要するにスライス。「インデクスで split」って、これだけでしょ?:

1 >>> s = "5137"
2 >>> s[:2], s[2:]
3 ('51', '37')

本質はスライスにしかなくて、スライスがあるのであえて名前付きの機能としてはなくても良い、と。

まぁ一般論としてはそうなんだけど、たまーにこういう処理が集中することがあるのよね。要するに名前に情報を含んでいるようなものを扱うような場合よね。例えば国土地理院の基盤地図情報数値標高モデルのファイル名は例えば「FG-GML-4839-56-30-DEM5A-20130702.xml」で、「4839-56-30」部分は、「(48, 39)」(一次メッシュ番号)、「(5, 6)」(二次メッシュ番号)、「(3, 0)」(三次メッシュ番号)と解釈することが出来るようになっている。

じゃあ仮に、名前付きで「split_by_index」があれば嬉しいだろうか、と考えてみる。まず、これがない世界はこうだ:

1 >>> mn1, mn2, mn3 = "4839", "56", "30"
2 >>> mesh1idx = int(mn1[:2]), int(mn1[2:])
3 >>> mesh2idx = int(mn2[:1]), int(mn2[1:])
4 >>> mesh3idx = None
5 >>> if mn3:  # 三次メッシュ番号はある場合とない場合がある、とする。
6 ...     mesh3idx = int(mn3[:1]), int(mn3[1:])
7 ...
8 >>> mesh1idx, mesh2idx, mesh3idx
9 ((48, 39), (5, 6), (3, 0))

インデクスがコード中に2回登場させなければならないのがややストレスに感じるのと、「int祭り」ねばならぬのが、うざいといえばうざい。正直「若干ダルい」以上の問題はない。そりゃそうだ。

split_by_index がある世界:

 1 >>> def split_by_index(s, idx):
 2 ...     return s[:idx], s[idx:]
 3 ...
 4 >>> mn1, mn2, mn3 = "4839", "56", "30"
 5 >>> mesh1idx = map(int, split_by_index(mn1, 2))
 6 >>> mesh2idx = map(int, split_by_index(mn2, 1))
 7 >>> mesh3idx = None
 8 >>> if mn3:
 9 ...     mesh3idx = map(int, split_by_index(mn3, 1))
10 ...
11 >>> mesh1idx, mesh2idx, mesh3idx
12 ([48, 39], [5, 6], [3, 0])

map が使えて、渡すインデクスが都度一回だけで良いというメリットの割には印象が良くないのは、名前が長いせいだ。けれど、既存のメソッド群から考えると、この命名はおそらく最善のものだ、削れない。

じゃあ現実世界ではワタシならどう書くだろうか、と考えると、結局多分こうする:

lamda か def かはお好みで。
1 >>> _fun = lambda s, idx: map(int, (s[:idx], s[idx:])) if s else None
2 >>> mesh1idx = _fun(mn1, 2)
3 >>> mesh2idx = _fun(mn2, 1)
4 >>> mesh3idx = _fun(mn3, 1)
5 >>> 
6 >>> mesh1idx, mesh2idx, mesh3idx
7 ([48, 39], [5, 6], [3, 0])

「Noneチェック」と map(int,という2つの「カスタマイズ」まですることになると、split_by_index があってもなくてもあんまり関係ない。ふむ、やはり、split_by_index が「必要だ」ということは、あんましなさそうな気がする。

なんてことを考えていると、既存のメソッドにも存在が不思議なものもチラホラあって。例えば「partition」。こんなだね:

1 >>> "abc,fghijk".partition(",")
2 ('abc', ',', 'fghijk')
3 >>> "abc,fgh,ijk".partition(",")
4 ('abc', ',', 'fgh,ijk')
5 >>> "abc,fgh,ijk".rpartition(",")
6 ('abc,fgh', ',', 'ijk')

ワタシは自分のプログラムで使ったことはないんだけどね。ある特定の状況で便利なのは理解は出来る。例えば入力がこんなだとしたら? :

 1 00001|#! /bin/env python
 2 00002|# -*- coding: utf-8 -*-
 3 00003|import sys
 4 00004|import os
 5 00005|import tarfile
 6 00006|import zipfile
 7 00007|import StringIO
 8 00008|import calendar
 9 00009|import datetime
10 00010|import argparse
11 00011|

処理結果の再利用性に無頓着な「昔ふうの」非 Unix マナーのプログラムには多いね、こんな出力。partition は実際のところ、なくても「split_by_index」が書けるなら、簡単に書けるであろう。partition は CPython では C で書かれているが、pure Python で書くとしたら多分こうだろう:

1     # sepが含まれない場合の処理は省略
2     # (本物はこの場合 head に元の文字列を返す)
3     def partition(self, sep):
4         idx = self.index(sep)
5         return self[:idx], self[idx:idx + 1], self[idx + 1:]

ある意味 partition と split_by_index の存在意義は、同程度なんじゃなかろうか、と思ったりもする。

シーケンスの分割、ということに関しては、「固定長さごとにブツ切る」というタスクも、たまーにはあるかもしれない。「2文字ずつのリストにする」なんてのはそんなにはないだろうが、「64文字ずつ」みたいなことなら「ないことはなさそうだ」と想像つきやすいかも。これは partition、split_by_index ほどには「どっちでも良い」くはなくて、要するにややダルい:

1 >>> def split_into_nsize_pieces(seq, length):
2 ...     for i in range(0, len(seq), length):
3 ...         yield seq[i:i + length]
4 ...
5 >>> import base64
6 >>> list(split_into_nsize_pieces(base64.encodestring(open("converter.py", "rb").read()), 77))[0]
7 'IyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KaW1wb3J0IHJlCmltcG9ydCBvcwp0cnk6CiAgICAjIGZv\n'

ので、名前付きで定義済みであれば、嬉しいことは嬉しい可能性はある。ただ、あえて上で適用したのが base64 文字列なのには意味があって。いっけんすると「行幅固定なので split_into_nsize_pieces が使えるぜ!」と思ってしまいそうだが、現実には base64.encodestring が適切に改行コードを入れる(入れてしまう)ので、分割したければ s.split(“\n”) でいいし、分割せずにそのまま出力したっていい。要するに、「やっぱりそんなにお世話にならない」であろうなと。そもそも固定幅フォーマットであっても、列幅は「2列3列2列5列」のように各々違うのが普通でしょうしな。

split_into_nsize_pieces 的なものが本当に「ユーティリティ」として価値を持つには、与える length で「列幅は「2列3列2列5列」のように各々違うのが普通」に耐えれるようにすること、かなと思う。けれど、正直これが一般的なタスクとは思えない。固定長フォーマットのテキストなんか、いまどきはなくなっていく一方なのだから。

なんてことを、「そういえば Python にはインデクスで split ってないよな?」から考えた、ってハナシでした。をしまい。