Python で camel case → snake case

Python で camel case → snake case もネット検索するとたくさん方法が紹介されてる。
しかし、よく紹介されてる方法は、本当に期待どおりか疑問がのこる。

# -*- coding: UTF-8 -*-
import re
str = "abcDefGhi2j"
str2 = "AbcDefGhi2j"

res = re.sub("([A-Z])",lambda x:"_" + x.group(1), str).lower()
res2 = re.sub("([A-Z])",lambda x:"_" + x.group(1), str2).lower()

print("%s → %s" % (str , res))
print("%s → %s" % (str2, res2))

結果は、、

abcDefGhi2j → abc_def_ghi2j
AbcDefGhi2j → _abc_def_ghi2j

2番目、str2 の "A"が、"_a" になってしまう。これを "a" にする方法は、
大文字1文字正規表現ではなく、任意1文字+大文字1文字で処理する方法にすることだ。

res2 = re.sub("(.[A-Z])",lambda x:x.group(1)[0] + "_" +x.group(1)[1], str2).lower()
print("%s → %s" % (str2, res2))

結果、、

AbcDefGhi2j → abc_def_ghi2j

ラムダなんて使わない方法では、、

res2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', re.sub('(.)([A-Z][a-z]+)', r'\1_\2', str2)).lower()

という手段もある。

変換後の snake case 文字列が全て小文字か?大文字か?どちらが良いか選択できる
方が良いので、メソッド化する。

def toSnakeCase(string, upper=False):
    import re
    if upper:
        return re.sub("(.[A-Z])",
                      lambda x: x.group(1)[0] + "_" + x.group(1)[1],
                      string).lower().upper()
    else:
        return re.sub("(.[A-Z])",
                      lambda x:x.group(1)[0] + "_" +x.group(1)[1],
                      string).lower()

第2引数=True なら、結果全て大文字だ。

res = toSnakeCase(str2, True)
print("%s → %s" % (str2 , res))

結果、、

AbcDefGhi2j → ABC_DEF_GHI2J