Элегантная функция Python для преобразования CamelCase в snake_case?

Пример:

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

Ответ 1

Это довольно подробно:

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

Ответ 2

В индексе пакета есть библиотека флексов, которая может обрабатывать эти вещи для вас. В этом случае вы будете искать inflection.underscore():

>>> inflection.underscore('CamelCase')
'camel_case'

Ответ 3

Я не знаю, почему все это так сложно.

в большинстве случаев простое выражение ([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])) может обрабатывать практически каждый сценарий в книге

>>> 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!

Ответ 4

Лично я не уверен, что что-либо, использующее регулярные выражения в python, может быть описано как элегантное. Большинство ответов здесь просто делают трюки типа "Code Golf" типа 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'

Ответ 5

stringcase - моя библиотека для этого; например:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'

Ответ 6

''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()

Ответ 7

Я не понимаю, зачем использовать оба вызова .sub()?:) Я не регулярный гуру, но я упростил функцию к этому, что подходит для моих определенных потребностей, мне просто нужно было решение для преобразования camelCasedVars из запроса POST в vars_with_underscore:

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

Он не работает с такими именами, как getHTTPResponse, потому что я слышал, что это плохое соглашение об именах (должно быть похоже на getHttpResponse, очевидно, что он намного легче запоминает эту форму).

Ответ 8

Я думаю, что это решение более прямолинейно, чем предыдущие ответы:

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 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

Регулярное выражение соответствует трем шаблонам:

  • [A-Z]?[a-z]+: последовательные строчные буквы, которые необязательно начинаются с буквы верхнего регистра.
  • [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): Два или более последовательных прописных буквы. Он использует lookahead, чтобы исключить последнюю букву верхнего регистра, если за ней следует строчная буква.
  • \d+: последовательные номера.

Используя re.findall, мы получаем список отдельных "слов", которые могут быть преобразованы в нижний регистр и соединены с символами подчеркивания.

Ответ 9

Здесь мое решение:

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 так, как должен.

Ответ 10

Для удовольствия:

>>> 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'

Ответ 11

Не в стандартной библиотеке, но я нашел этот script, который, как представляется, содержит необходимые вам функции.

Ответ 12

Это не элегантный метод, это очень "низкий уровень" реализации простой конечной машины (конечный автомат битполя), возможно, самый антипитонический режим для решения этой проблемы, однако модуль также реализует слишком сложный конечный автомат для разрешите эту простую задачу, поэтому я думаю, что это хорошее решение.

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 может анализировать все типы case: UpperSEQUENCEInterleaved, under_score, BIG_SYMBOLS и cammelCasedMethods

Я надеюсь, что это полезно

Ответ 13

Так много сложных методов... Просто найдите всю группу "Titled" и присоедините ее вариант в нижнем регистре с подчеркиванием.

>>> 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'

Если вы не хотите, чтобы числа были похожи на первый символ группы или отдельной группы - вы можете использовать маску ([Az][a-z0-9]*).

Ответ 14

Ух ты, я просто украл это из фрагментов Джанго. ссылка 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'

REGEX DEMO

Ответ 15

Использование регулярных выражений может быть самым коротким, но это решение более читаемо:

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

Ответ 16

Легко адаптирован из 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()

Ответ 17

Взгляните на превосходный Schematics lib

https://github.com/schematics/schematics

Он позволяет создавать типизированные структуры данных, которые могут сериализоваться/десериализоваться с питона на 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')

Ответ 18

Я предпочитаю избегать повтора, если это возможно:

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

Ответ 19

Ужасный пример с использованием регулярных выражений (вы можете легко его очистить:)):

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")

EDIT: также должно быть довольно легко увидеть, что есть место для улучшения для таких случаев, как "Тест", потому что подчеркивание безоговорочно вставлено.

Ответ 20

Очень хороший RegEx, предложенный на этот сайт:

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

Если python имеет метод String Split, он должен работать...

В Java:

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

Ответ 21

Здесь я сделал что-то, чтобы изменить заголовки в файле с разделителями табуляции. Я опускаю часть, где я только редактировал первую строку файла. Вы можете легко адаптировать его к Python с помощью библиотеки re. Это также включает разделение номеров (но сохраняет цифры вместе). Я сделал это за два шага, потому что это было проще, чем сказать, что не стоит подчеркивать в начале строки или вкладки.

Шаг первый... найдите заглавные буквы или целые числа, которым предшествуют строчные буквы, и предварите их символом подчеркивания:

Поиск:

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

Замена:

\1_\l\2/

Шаг второй... возьмите вышеуказанное и запустите его снова, чтобы преобразовать все кепки в нижний регистр:

Поиск:

([A-Z])

Замена (это обратная косая черта, нижний регистр L, обратная косая черта, одна):

\l\1

Ответ 22

Я искал решение той же проблемы, за исключением того, что мне нужна цепочка; например.

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

Начиная с приятных двухсловных решений, я придумал следующее:

"-".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"))

Большая часть сложной логики заключается в том, чтобы избежать сглаживания первого слова. Здесь более простая версия, если вы не возражаете изменить первое слово:

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

Конечно, вы можете предварительно скомпилировать регулярные выражения или присоединиться к подчеркиванию вместо дефиса, как описано в других решениях.

Ответ 23

Краткий без регулярных выражений, но 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())])

Ответ 24

Без какой-либо библиотеки:

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

Ответ 25

Этот простой метод должен выполнять следующее задание:

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()
  • Мы ищем заглавные буквы, которым предшествует любое количество (или нулевых) заглавных букв, за которым следует любое количество строчных символов.
  • Подчеркивание помещается непосредственно перед вступлением последней заглавной буквы, найденной в группе, и ее можно разместить до этой заглавной буквы, если ей предшествуют другие заглавные буквы.
  • Если есть нижние символы подчеркивания, удалите их.
  • Наконец, вся строка результата изменяется на нижний регистр.

(взято из здесь, см. рабочий пример онлайн)

Ответ 26

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()

Ответ 27

На случай, если кому-то понадобится преобразовать полный исходный файл, вот скрипт, который это сделает.

# 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))

Ответ 28

Мне очень повезло с этим:

import re
def camelcase_to_underscore(s):
    return re.sub(r'(^|[a-z])([A-Z])',
                  lambda m: '_'.join([i.lower() for i in m.groups() if i]),
                  s)

Это может быть оптимизировано для скорости, если вы хотите.

import re

CC2US_RE = re.compile(r'(^|[a-z])([A-Z])')

def _replace(match):
    return '_'.join([i.lower() for i in match.groups() if i])

def camelcase_to_underscores(s):
    return CC2US_RE.sub(_replace, s)

Ответ 29

def convert(camel_str):
    temp_list = []
    for letter in camel_str:
        if letter.islower():
            temp_list.append(letter)
        else:
            temp_list.append('_')
            temp_list.append(letter)
    result = "".join(temp_list)
    return result.lower()

Ответ 30

Используйте: str.capitalize() для преобразования первой буквы строки (содержащейся в переменной str) в заглавную букву и возвращает всю строку.

Пример: Команда: "привет".capitalize() Вывод: Hello