web-dev-qa-db-ja.com

CamelCaseをsnake_caseに変換するエレガントなPython関数?

例:

>>> convert('CamelCase')
'camel_case'
334

これはかなり徹底的です:

def convert(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

これらすべてで動作します(すでにキャメルされていないバージョンを害しません):

>>> convert('CamelCase')
'camel_case'
>>> convert('CamelCamelCase')
'camel_camel_case'
>>> convert('Camel2Camel2Case')
'camel2_camel2_case'
>>> convert('getHTTPResponseCode')
'get_http_response_code'
>>> convert('get2HTTPResponseCode')
'get2_http_response_code'
>>> convert('HTTPResponseCode')
'http_response_code'
>>> convert('HTTPResponseCodeXYZ')
'http_response_code_xyz'

または、それを何億回も呼び出す場合は、正規表現をプリコンパイルできます。

first_cap_re = re.compile('(.)([A-Z][a-z]+)')
all_cap_re = re.compile('([a-z0-9])([A-Z])')
def convert(name):
    s1 = first_cap_re.sub(r'\1_\2', name)
    return all_cap_re.sub(r'\1_\2', s1).lower()

正規表現モジュールをインポートすることを忘れないでください

import re
674
epost

パッケージインデックスには、これらを処理できる inflection library があります。この場合、 inflection.underscore() を探していることになります。

>>> inflection.underscore('CamelCase')
'camel_case'
146
Brad Koch

これらがどれほど複雑なのかはわかりません。

ほとんどの場合、単純な式([A-Z]+)がトリックを行います

>>> re.sub('([A-Z]+)', r'_\1','CamelCase').lower()
'_camel_case'  
>>> re.sub('([A-Z]+)', r'_\1','camelCase').lower()
'camel_case'
>>> re.sub('([A-Z]+)', r'_\1','camel2Case2').lower()
'camel2_case2'
>>> re.sub('([A-Z]+)', r'_\1','camelCamelCase').lower()
'camel_camel_case'
>>> re.sub('([A-Z]+)', r'_\1','getHTTPResponseCode').lower()
'get_httpresponse_code'

最初の文字を無視するには、単に(?!^)の後ろにルックを追加します

>>> re.sub('(?!^)([A-Z]+)', r'_\1','CamelCase').lower()
'camel_case'
>>> re.sub('(?!^)([A-Z]+)', r'_\1','CamelCamelCase').lower()
'camel_camel_case'
>>> re.sub('(?!^)([A-Z]+)', r'_\1','Camel2Camel2Case').lower()
'camel2_camel2_case'
>>> re.sub('(?!^)([A-Z]+)', r'_\1','getHTTPResponseCode').lower()
'get_httpresponse_code'

ALLCapsをall_capsに分離し、文字列内の数値を期待する場合は、|この式((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))を使用するだけで、2回の実行を行う必要はありません。

>>> a = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))')
>>> a.sub(r'_\1', 'getHTTPResponseCode').lower()
'get_http_response_code'
>>> a.sub(r'_\1', 'get2HTTPResponseCode').lower()
'get2_http_response_code'
>>> a.sub(r'_\1', 'get2HTTPResponse123Code').lower()
'get2_http_response123_code'
>>> a.sub(r'_\1', 'HTTPResponseCode').lower()
'http_response_code'
>>> a.sub(r'_\1', 'HTTPResponseCodeXYZ').lower()
'http_response_code_xyz'

それはすべてあなたが望むものに依存するので、過度に複雑であってはならないので、あなたのニーズに最適なソリューションを使用してください。

nJoy!

87
nickl-

個人的には、pythonで正規表現を使用しているものがどのようにエレガントであると説明できるのかわかりません。ここでのほとんどの回答は、「コードゴルフ」タイプのREトリックを行うことです。エレガントなコーディングは簡単に理解できるはずです。

def to_snake_case(not_snake_case):
    final = ''
    for i in xrange(len(not_snake_case)):
        item = not_snake_case[i]
        if i < len(not_snake_case) - 1:
            next_char_will_be_underscored = (
                not_snake_case[i+1] == "_" or
                not_snake_case[i+1] == " " or
                not_snake_case[i+1].isupper()
            )
        if (item == " " or item == "_") and next_char_will_be_underscored:
            continue
        Elif (item == " " or item == "_"):
            final += "_"
        Elif item.isupper():
            final += "_"+item.lower()
        else:
            final += item
    if final[0] == "_":
        final = final[1:]
    return final

>>> to_snake_case("RegularExpressionsAreFunky")
'regular_expressions_are_funky'

>>> to_snake_case("RegularExpressionsAre Funky")
'regular_expressions_are_funky'

>>> to_snake_case("RegularExpressionsAre_Funky")
'regular_expressions_are_funky'
15
TehTris

stringcase はこのための私のライブラリです;例えば。:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'
11
Beau
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
8
Jimmy

両方の.sub()呼び出しを使用する理由がわかりませんか? :)私は正規表現の第一人者ではありませんが、特定のニーズに適したこの関数に単純化しました。POSTリクエストからvars_with_underscoreにcamelCasedVarsを変換するソリューションが必要でした。

def myFunc(...):
  return re.sub('(.)([A-Z]{1})', r'\1_\2', "iTriedToWriteNicely").lower()

GetHTTPResponseのような名前では動作しません。命名規則が悪いと聞いたからです(getHttpResponseのように、このフォームを覚える方がずっと簡単です)。

5
desper4do

このソリューションは以前の回答よりも簡単だと思います。

import re

def convert (camel_input):
    words = re.findall(r'[A-Z]?[a-z]+|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)|\d+', camel_input)
    return '_'.join(map(str.lower, words))


# Let's test it
test_strings = [
    'CamelCase',
    'camelCamelCase',
    'Camel2Camel2Case',
    'getHTTPResponseCode',
    'get200HTTPResponseCode',
    'getHTTP200ResponseCode',
    'HTTPResponseCode',
    'ResponseHTTP',
    'ResponseHTTP2',
    'Fun?!awesome',
    'Fun?!Awesome',
    '10CoolDudes',
    '20coolDudes'
]
for test_string in test_strings:
    print(convert(test_string))

どの出力:

camel_case
camel_camel_case
camel_2_camel_2_case
get_http_response_code
get_200_http_response_code
get_http_200_response_code
http_response_code
response_http
response_http_2
fun_awesome
fun_awesome
10_cool_dudes
20_cool_dudes

正規表現は3つのパターンに一致します。

  1. [A-Z]?[a-z]+:オプションで大文字で始まる連続した小文字。
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$):2つ以上の連続した大文字。小文字が続く場合、先読みを使用して最後の大文字を除外します。
  3. \d+:連続した数字。

re.findallを使用することにより、小文字に変換してアンダースコアで結合できる個々の「単語」のリストを取得します。

5
rspeed

私のソリューションは次のとおりです。

def un_camel(text):
    """ Converts a CamelCase name into an under_score name. 

        >>> un_camel('CamelCase')
        'camel_case'
        >>> un_camel('getHTTPResponseCode')
        'get_http_response_code'
    """
    result = []
    pos = 0
    while pos < len(text):
        if text[pos].isupper():
            if pos-1 > 0 and text[pos-1].islower() or pos-1 > 0 and \
            pos+1 < len(text) and text[pos+1].islower():
                result.append("_%s" % text[pos].lower())
            else:
                result.append(text[pos].lower())
        else:
            result.append(text[pos])
        pos += 1
    return "".join(result)

コメントで説明されているこれらのコーナーケースをサポートします。たとえば、getHTTPResponseCodeを必要に応じてget_http_response_codeに変換します。

4
Evan Fosmark

それの楽しみのために:

>>> def un_camel(input):
...     output = [input[0].lower()]
...     for c in input[1:]:
...             if c in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
...                     output.append('_')
...                     output.append(c.lower())
...             else:
...                     output.append(c)
...     return str.join('', output)
...
>>> un_camel("camel_case")
'camel_case'
>>> un_camel("CamelCase")
'camel_case'

または、楽しみのためにもっと:

>>> un_camel = lambda i: i[0].lower() + str.join('', ("_" + c.lower() if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" else c for c in i[1:]))
>>> un_camel("camel_case")
'camel_case'
>>> un_camel("CamelCase")
'camel_case'
3
gahooa

非常に多くの複雑なメソッド...すべての「タイトル付き」グループを見つけて、小文字のバリアントをアンダースコアで結合します。

>>> import re
>>> def camel_to_snake(string):
...     groups = re.findall('([A-z0-9][a-z]*)', string)
...     return '_'.join([i.lower() for i in groups])
...
>>> camel_to_snake('ABCPingPongByTheWay2KWhereIsOurBorderlands3???')
'a_b_c_ping_pong_by_the_way_2_k_where_is_our_borderlands_3'

グループまたは別のグループの最初の文字のような数字を作りたくない場合は、([A-z][a-z0-9]*)マスクを使用できます。

2
unitto

これはエレガントな方法ではなく、シンプルなステートマシン(ビットフィールドステートマシン)の非常に「低レベル」な実装であり、おそらくこれを解決するための最も反Python的なモードですが、reモジュールはこのシンプルな解決のために複雑すぎるステートマシンも実装しますタスクなので、これは良い解決策だと思います。

def splitSymbol(s):
    si, ci, state = 0, 0, 0 # start_index, current_index 
    '''
        state bits:
        0: no yields
        1: lower yields
        2: lower yields - 1
        4: upper yields
        8: digit yields
        16: other yields
        32 : upper sequence mark
    '''
    for c in s:

        if c.islower():
            if state & 1:
                yield s[si:ci]
                si = ci
            Elif state & 2:
                yield s[si:ci - 1]
                si = ci - 1
            state = 4 | 8 | 16
            ci += 1

        Elif c.isupper():
            if state & 4:
                yield s[si:ci]
                si = ci
            if state & 32:
                state = 2 | 8 | 16 | 32
            else:
                state = 8 | 16 | 32

            ci += 1

        Elif c.isdigit():
            if state & 8:
                yield s[si:ci]
                si = ci
            state = 1 | 4 | 16
            ci += 1

        else:
            if state & 16:
                yield s[si:ci]
            state = 0
            ci += 1  # eat ci
            si = ci   
        print(' : ', c, bin(state))
    if state:
        yield s[si:ci] 


def camelcaseToUnderscore(s):
    return '_'.join(splitSymbol(s)) 

splitsymbolは、すべてのケースタイプを解析できます:UpperSEQUENCEInterleaved、under_score、BIG_SYMBOLS、およびcammelCasedMethods

役に立つことを願っています

2
jdavidls

標準ライブラリにはありませんが、必要な機能が含まれているように見える このスクリプト が見つかりました。

2
Stefano Borini

優れたSchematicsライブラリをご覧ください

https://github.com/schematics/schematics

pythonからJavascriptフレーバーにシリアライズ/デシリアライズできる型付きデータ構造を作成できます。例:

class MapPrice(Model):
    price_before_vat = DecimalType(serialized_name='priceBeforeVat')
    vat_rate = DecimalType(serialized_name='vatRate')
    vat = DecimalType()
    total_price = DecimalType(serialized_name='totalPrice')
1
Iain Hunter

正規表現を使用するのが最も短いかもしれませんが、このソリューションははるかに読みやすいです:

def to_snake_case(s):
    snake = "".join(["_"+c.lower() if c.isupper() else c for c in s])
    return snake[1:] if snake.startswith("_") else snake
1
3k-

Djangoスニペットからこれを盗んだだけです。 ref http://djangosnippets.org/snippets/585/

かなりエレガント

camelcase_to_underscore = lambda str: re.sub(r'(?<=[a-z])[A-Z]|[A-Z](?=[^A-Z])', r'_\g<0>', str).lower().strip('_')

例:

camelcase_to_underscore('ThisUser')

戻り値:

'this_user'

正規表現デモ

1
brianray

私は可能な限り再回避を好む:

myString="ThisStringIsCamelCase" ''.join(['_'+i.lower() if i.isupper() else i for i in myString]).lstrip('_') 'this_string_is_camel_case'

1
otocan

https://stackoverflow.com/users/267781/matth ジェネレーターを使用する人から簡単に適応。

def uncamelize(s):
    buff, l = '', []
    for ltr in s:
        if ltr.isupper():
            if buff:
                l.append(buff)
                buff = ''
        buff += ltr
    l.append(buff)
    return '_'.join(l).lower()
1
Salvatore

タブ区切りファイルのヘッダーを変更するために私がしたことを次に示します。ファイルの最初の行のみを編集した部分は省略しています。 reライブラリを使用すると、Pythonに簡単に適合させることができます。これには、数字の分離も含まれます(ただし、数字は一緒に保持されます)。行またはタブの先頭にアンダースコアを入れないように指示するよりも簡単だったため、2つのステップで実行しました。

ステップ1:大文字または小文字の前にある整数を見つけ、それらの前にアンダースコアを付けます:

サーチ:

([a-z]+)([A-Z]|[0-9]+)

置換:

\1_\l\2/

ステップ2 ...上記を実行し、再度実行して、すべての大文字を小文字に変換します。

サーチ:

([A-Z])

置換(バックスラッシュ、小文字のL、バックスラッシュ、1つ):

\l\1
0
Joe Tricarico

誰かが完全なソースファイルを変換する必要がある場合に備えて、これを実行するスクリプトを次に示します。

# Copy and paste your camel case code in the string below
camelCaseCode ="""
    cv2.Matx33d ComputeZoomMatrix(const cv2.Point2d & zoomCenter, double zoomRatio)
    {
      auto mat = cv2.Matx33d::eye();
      mat(0, 0) = zoomRatio;
      mat(1, 1) = zoomRatio;
      mat(0, 2) = zoomCenter.x * (1. - zoomRatio);
      mat(1, 2) = zoomCenter.y * (1. - zoomRatio);
      return mat;
    }
"""

import re
def snake_case(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

def lines(str):
    return str.split("\n")

def unlines(lst):
    return "\n".join(lst)

def words(str):
    return str.split(" ")

def unwords(lst):
    return " ".join(lst)

def map_partial(function):
    return lambda values : [  function(v) for v in values]

import functools
def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: f(g(x)), functions, lambda x: x)

snake_case_code = compose(
    unlines ,
    map_partial(unwords),
    map_partial(map_partial(snake_case)),
    map_partial(words),
    lines
)
print(snake_case_code(camelCaseCode))
0
Pascal T.

非常に素晴らしい正規表現が このサイト で提案されました:

(?<!^)(?=[A-Z])

pythonにString Splitメソッドがある場合、動作するはずです...

Javaの場合:

String s = "loremIpsum";
words = s.split("(?&#60;!^)(?=[A-Z])");
0
Jmini

この簡単な方法で仕事ができます:

import re

def convert(name):
    return re.sub(r'([A-Z]*)([A-Z][a-z]+)', lambda x: (x.group(1) + '_' if x.group(1) else '') + x.group(2) + '_', name).rstrip('_').lower()
  • 任意の数の(またはゼロの)大文字が先行し、その後に任意の数の小文字が続く大文字を探します。
  • アンダースコアは、グループ内の最後の大文字が現れる直前に配置され、他の大文字が先行する場合には、その大文字の前に配置できます。
  • 末尾にアンダースコアがある場合は、それらを削除します。
  • 最後に、結果文字列全体が小文字に変更されます。

ここ から取得、 オンラインの作業例 を参照)

0
Mathieu Rodic
def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() else '') + y, 
        name
    ).lower()

また、既にキャメルされていない入力でケースをカバーする必要がある場合:

def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() and not x.endswith('_') else '') + y, 
        name
    ).lower()
0
dmrz

正規表現を使用した恐ろしい例(easily this clean up :)):

def f(s):
    return s.group(1).lower() + "_" + s.group(2).lower()

p = re.compile("([A-Z]+[a-z]+)([A-Z]?)")
print p.sub(f, "CamelCase")
print p.sub(f, "getHTTPResponseCode")

ただし、getHTTPResponseCodeでも機能します!

あるいは、ラムダを使用して:

p = re.compile("([A-Z]+[a-z]+)([A-Z]?)")
print p.sub(lambda x: x.group(1).lower() + "_" + x.group(2).lower(), "CamelCase")
print p.sub(lambda x: x.group(1).lower() + "_" + x.group(2).lower(), "getHTTPResponseCode")

編集:アンダースコアが無条件に挿入されるため、「テスト」のような場合には改善の余地があることも簡単にわかるはずです。

0
Matthew Iselin

正規表現なしで簡潔に、ただしHTTPResponseCode => httpresponse_code:

def from_camel(name):
    """
    ThisIsCamelCase ==> this_is_camel_case
    """
    name = name.replace("_", "")
    _cas = lambda _x : [_i.isupper() for _i in _x]
    seq = Zip(_cas(name[1:-1]), _cas(name[2:]))
    ss = [_x + 1 for _x, (_i, _j) in enumerate(seq) if (_i, _j) == (False, True)]
    return "".join([ch + "_" if _x in ss else ch for _x, ch in numerate(name.lower())])
0
Dantalion

ライブラリなし:

def camelify(out):
    return (''.join(["_"+x.lower() if i<len(out)-1 and x.isupper() and out[i+1].islower()
         else x.lower()+"_" if i<len(out)-1 and x.islower() and out[i+1].isupper()
         else x.lower() for i,x in enumerate(list(out))])).lstrip('_').replace('__','_')

少し重いけど

CamelCamelCamelCase ->  camel_camel_camel_case
HTTPRequest         ->  http_request
GetHTTPRequest      ->  get_http_request
getHTTPRequest      ->  get_http_request
0
bibmartin

私は、チェーンが必要だったことを除いて、同じ問題の解決策を探していました。例えば.

"CamelCamelCamelCase" -> "Camel-camel-camel-case"

ここのニースの2ワードソリューションから始めて、私は次のことを思いつきました。

"-".join(x.group(1).lower() if x.group(2) is None else x.group(1) \
         for x in re.finditer("((^.[^A-Z]+)|([A-Z][^A-Z]+))", "stringToSplit"))

複雑なロジックのほとんどは、最初のWordの小文字化を避けることです。最初のWordを変更してもかまわない場合の簡単なバージョンを次に示します。

"-".join(x.group(1).lower() for x in re.finditer("(^[^A-Z]+|[A-Z][^A-Z]+)", "stringToSplit"))

もちろん、他のソリューションで説明したように、正規表現をプリコンパイルするか、ハイフンの代わりにアンダースコアで結合することができます。

0
Jim Pivarski