Python 始めました

Python の入門書を2冊読んで Python のことがだいたいわかったような気になったので、スクリプトを書いてみた。 1234567890 を 1,234,567,890 に変換するスクリプトだ。 (print(f'{1234567890:,}') のことは知っているが…)

たぶんこれが正解だろう。


import re

x = 1234567890                      # 1234567890 を x に代入
x = str(x)                          # 数値を文字列に変換して x に代入
m = len(x) % 3                      # x の長さを3で除算し、剰余を m に代入
lst = []                            # リスト lst を作成、要素はない、空っぽ
                                    #
if m > 0:                           # もし剰余が0以上ならば
    lst.append(x[:m])               # x の先頭から、剰余と同じ個数の文字を lst に追加
    x = x[m:]                       # x に x の m 番目以降を代入
                                    #
lst += re.findall('\d{3}', x)       # x の \d{3}(数字が3つ)に一致したすべての文字列を lst に追加
                                    #
print(','.join(lst))                # lst の要素を , で連結して出力

x[m:]、[start:step] は start の位置からstep 個分を意味する。start を省略すれば最初から、step を省略すれば最後までだ。 m 番目についてだが、文字列の先頭は1番ではなく、0番だ。1、2、3… でなはく、0、1、2… だ。 x が '234567890' であれば re.findall('\d{3}', x) は ['234', '567', '890'] を返す。lst += re.findall('\d{3}', x) は lst = lst + re.findall('\d{3}', x) のことだ。
これを Ruby に翻訳すると、


# Ruby

x = 1234567890
x = x.to_s
l = x.size
m = l % 3
ary = []

if m > 0
  ary.push(x[0,m])
  x = x[m,l]
end

ary += x.scan(/\d{3}/)

print ary.join(',')

ほぼ同じだ。

もし、最初から Ruby で書くのであれば、末尾から処理するスクリプトを書く。除算はしない。


# Ruby

x = 1234567890
x = x.to_s
ary = []

until x == ""
  x.sub!(/\d{1,3}$/, "")
  ary.unshift($&)
end

print ary.join(',')

until 文の部分は、x.sub!(/\d{1,3}$/, "") and ary.unshift($&) until x == "" と一行で書くだろう。
/\d{1,3}$/ は正規表現。\d は数字、$ は行末。{1,3} は1つ以上3つまでの繰り返しで、/\d{1,3}$/ は 1234 なら最長の3つの数字 234 に一致し、12 なら 12 に一致する。 sub! は置換だが、破壊的メソッドと呼ばれている。sub との違いは元の文字列自身を変更する。$& は正規表現に一致したパターンが代入された特殊変数。

これを Python に翻訳すると、


import re

x = 1234567890
x = str(x)
lst = []

while len(x) > 0:
    lst.insert(0, re.search('\d{1,3}$', x).group())
    x = re.sub('\d{1,3}$', '', x)

print(','.join(lst))

正規表現を使うのであれば import re でライブラリを読み込む必要がある。
Python に until はない。繰り返しは while と for だけだ。
Ruby では正規表現に一致したパターンは $& で参照するが、Python では group() を使用する。
めんどうだな…

末尾からではなく行頭から、条件式が成立している間ではなく指定した回数で繰り返すのであれば、


# Ruby

x = 1234567890
x = x.to_s
l = x.size
q, m = l.divmod(3)
q += 1 if m > 0; q = 1 if l <= 3
m = 3 if m == 0
ary = Array.new

q.times{|i|
  i == 0 ? x.sub!(/\d{#{m}}/, "") : x.sub!(/\d{3}/, "")
  ary.push($&)
}

print ary.join(',')

三項演算子を使って if 文を一行で書いた。
剰余 m が1の場合、\d{m} と書いても \d{1} とは解釈されない。正規表現の中で変数を使用する場合は、\d{#{m}} と書く必要がある。

Python では、


import re

x = 1234567890
x = str(x)
l = len(x)
q, m = divmod(l, 3)
q += 1 if m > 0 else q; q = 1 if l <= 3 else q
m = 3 if m == 0 else m
lst = []
pattern = re.compile(r'^\d{%d}' %(m))

for i in range(q):
    if i == 0:
        lst.append(pattern.search(x).group())
        x = pattern.sub('', x)
    else:
        lst.append(re.search('^\d{1,3}', x).group())
        x = re.sub('^\d{1,3}', '', x)

print(','.join(lst))

Python にも三項演算子はあるが、処理の部分が2行なのでどう書けばよいのかちょっとわからない。
正規表現の中で変数を使用する場合は事前にコンパイルする必要がある。 re.compile(r'^\d{%d}' %(m)) だ。
コンパイルするのは我慢できるが、pattern.search(x) とはどういうことだ? re.search('^\d{1,3}', x) なら re.search('pattern', x) あるいは search('pattern', x) ではないのか? この理由は説明してほしい。

Python は可読性が高いといわれているが、直感的ではないというイメージだ。オブジェクト指向も徹底していないという気もする。


最初のスクリプトの findall を使用しない別のスクリプトも考えた。最後のスクリプトは、繰り返し文で else を使えば繰り返しが終わった時に1回だけ実行する処理が書けると知り試してみた。三項演算子も使ったのでスクリプトはちょっとだけ短くなった。


x = 1234567890
x = str(x)
lst = []
q, m = divmod(len(x), 3)

if m > 0:
    lst.append(x[:m])
    x = x[m:]

for i in range(q):
    lst.append(x[:3])
    x = x[3:]

print(','.join(lst))

x = 1234567890
x = str(x)
lst = []
m = len(x) % 3

for i in range(m, len(x)+1, 3):
    lst.append(x[:i]) if i = 3 else lst.append(x[i-3:i])
else:
    print(','.join(lst))