読者です 読者をやめる 読者になる 読者になる

methaneのブログ

このブログに乗せているサンプルコードはすべてNYSLです。

そんなに Array.join がいいのか

Python

「(ruby|javascript)でstr.join(array)、pythonでlist.join(str)」http://blog.livedoor.jp/dankogai/archives/51226075.html

ここではlistを継承したListクラスをこさえて、そこにjoinメソッドを追加しているが、listに直にメソッド追加する方法はないのだろうか....

多分できない。
PythonPerl,Rubyに比べて、上の方は同じくらい柔らかいんだけど、下の方は堅い。

Unicodeのことまで考慮に入れて実装したために、実装がずいぶんと泥臭いものになっている。

join したいものが一種類ではないというのが、 join が list のメソッドでない理由の一つだからね。

str()はUnicodeを食うと例外をお吐きになる。それでいて"dankogai" + u"小飼弾"はu"dankogai小飼弾"になってくれる。し、"+".join("dankogai" + u"小飼弾")も問題なくu"dankogai小飼弾"になってくれる。

ここは、Python初心者に取っては罠だ。(なのでpy3kではstrが撤去される)
str と unicode の関係は、例えば int と double のように、unicode 側に自動で格上げされるというものだ。なので u"foo" + "bar" の結果は u"foobar" になる。
格上げの際、str型にはascii文字列が入っているものとみなされ、ascii*1としてデコードできない場合はUnicodeDeocdeErrorが発生する。
逆に、str(u"foo") のように明示的に格下げする際も、ascii 以外の文字が入っていたら UnicodeEncodeError が発生する。
このことを理解すれば、合理的で一貫性が保たれているのが判るので、「それでいて」なんて思わなくなる。

さて、RubyのArray.join 相当の、勝手に中身を文字列に変換するjoinが欲しい場合、

def join(iterable, sep=''):
    return sep.join(unicode(x) for x in iterable)

で普通は充分だろう。やだやだ!unicodeが混じってない場合はstrじゃないとやだ!という場合は、

def join(iterable, sep=''):
    def s(x):
        if isinstance(x, basestring):
            return x
        return str(x)
    return sep.join(s(x) for x in iterable)

になるかな。文字列を += で繋げていくのは、文字列の長さに比例したコピーコストが発生して計算量が最悪 O(n^2) になる可能性があるのでお勧めできない。

というわけで、この件に関しては、「どちらがキモい」ではなく、「どちらが歩み寄りやすいか」という点においてPythonが見劣りするのは否めない。

他の言語に対しての「歩みより」は、Pythonが重視している価値ではないからね。
ブロック構文があればRubyistを取りこめるかもしれないけど、Pythonでは内包表記とwith文があるからブロック構文の必要性は皆無で、必要が無い機能は導入しない。

配列の中味をデリミタでjoin()する

にしても、

join the array elements with the delimiter

にしても、言葉に近いのは RubyJavaScript の方ではないか。

僕は主語の入れ替えができるという話をしたのに、通じなかったらしい。「紙をのりでくっつける」を「のりが紙をくっつける」と言い換えることが出きるみたいに、何を主語にするべきかなんてのは主観でしかないのに。

関数としてはjoin(delim, list)が、メソッドとしてはarray.join(delim)が落としどころのように感じられるのだが....

そんなに str.join が嫌なら import string; string.join() をどうぞ。
sep.join() が直感に反しているように思える人がいくら多くても、複数の文字列と複数のiterableを正しく扱うには、 str.join は合理的でも array.join は合理的ではない。

*1:sitecustomize.py で sys.setdefaultencoding('utf-8') をすることでascii以外のエンコーディングを指定可能