Função Python elegante para converter CamelCase em snake_case?

333

Exemplo:

>>> convert('CamelCase')
'camel_case'
Sridhar Ratnakumar
fonte
28
Para converter na outra direção, consulte esta outra questão de stackoverflow.
Nathan
10
nb que é NotCamelCase, masthisIs
Matt Richards
5
@MattRichards É uma questão de disputa. wiki
NO_NAME 5/08/15
@MattRichards Por exemplo, em Java, eles usam os dois, CamelCase é usado para nomear definições de classe, enquanto camelCase é usado para nomear variáveis ​​inicializadas.
darkless

Respostas:

796

Caso camelo para cobra

import re

name = 'CamelCaseName'
name = re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()
print(name)  # camel_case_name

Se você fizer isso muitas vezes e o acima for lento, compile o regex antecipadamente:

pattern = re.compile(r'(?<!^)(?=[A-Z])')
name = pattern.sub('_', name).lower()

Para lidar com casos mais avançados especialmente (isso não é mais reversível):

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

print(camel_to_snake('camel2_camel2_case'))  # camel2_camel2_case
print(camel_to_snake('getHTTPResponseCode'))  # get_http_response_code
print(camel_to_snake('HTTPResponseCodeXYZ'))  # http_response_code_xyz

Caso cobra para camelo

name = 'snake_case_name'
name = ''.join(word.title() for word in name.split('_'))
print(name)  # SnakeCaseName
epost
fonte
11
Essa solução falha nesses casos: _test_Method, __test__Method, _Test, getHTTPresponseCode, __CamelCase e _Camel_Case.
freegnu
6
e o contrário? Converter um not_camel_casepara notCamelCasee / ou NotCamelCase?
precisa saber é o seguinte
9
Para evitar sublinhados duplos ao converter, por exemplo, camel_Case, adicione esta linha:s2.replace('__', '_')
Marcus Ahlberg
2
Observe que isso não é muito reversível. getHTTPResponseCode deve ser convertido em get_h_t_t_p_response_code. O getHttpResponseCode deve ser convertido em get_http_response_code
K2xL 5/15
4
@AnmolSinghJaggi O primeiro regex lida com maiúsculas e minúsculas de um acrônimo seguido por outra palavra (por exemplo, "HTTPResponse" -> "HTTP_Response") OU o caso mais normal de uma palavra inicial em minúscula seguida de uma palavra em maiúscula (por exemplo, "getResponse" -> " get_Response ". O segundo regex lida com o caso normal de dois não acrônimos (por exemplo," ResponseCode "->" Response_Code ") seguido de uma chamada final para minúsculas. Assim," getHTTPResponseCode "->" getHTTP_ResponseCode "->" get_HTTP_Response_Code "- > "get_http_response_code"
Jeff Moser
188

Há uma biblioteca de inflexão no índice do pacote que pode lidar com essas coisas para você. Nesse caso, você procuraria inflection.underscore():

>>> inflection.underscore('CamelCase')
'camel_case'
Brad Koch
fonte
44
Não entendo por que as pessoas votam no uso de funções personalizadas quando há uma grande biblioteca que executa essa tarefa. Não devemos reinventar a roda.
oden
87
@oden Talvez porque a adição de uma nova dependência inteira para o trabalho de uma função de linha única seja uma fratura exagerada ?
Cecil Curry
11
Por um exemplo, com certeza é um exagero. Em um aplicativo maior, não há necessidade de reinventar e ofuscar a roda.
Brad Koch
11
Regexes retornam muito a uma "linha única", e é por isso que é muito mais do que uma linha com testes adequados.
Studgeek #
12
@CecilCurry: Tenho certeza de que você é um ótimo programador, mas não tenho certeza de que não há casos que você não considerou - basta ver outras respostas aqui para exemplos. É por isso que sempre escolherei uma biblioteca, porque é a soma da experiência de muitos mais desenvolvedores do que apenas eu.
Michael Scheper
104

Não sei por que tudo isso é tão complicado.

na maioria dos casos, a expressão simples ([A-Z]+)fará o truque

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

Para ignorar o primeiro caractere, basta adicionar look behind (?!^)

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

Se você deseja separar ALLCaps em all_caps e esperar números em sua string, ainda não precisa executar duas execuções separadas, basta usar |Esta expressão ((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))pode lidar com praticamente todos os cenários do livro

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

Tudo depende do que você deseja, então use a solução que melhor se adapte às suas necessidades, pois ela não deve ser muito complicada.

nJoy!

nickl-
fonte
11
A última iteração é a mais inteligente, IMO. Demorei um pouco para entender que ele está apenas substituindo o caractere único no início de cada palavra - e isso foi apenas porque a abordagem era diferente da que eu tinha inventado. Bem feito.
Justin Miller
2
Fiquei intrigado com a (?!^)expressão sendo chamada de olhar para trás. A menos que eu esteja perdendo alguma coisa, o que realmente queremos aqui é um olhar negativo por trás, que deve ser expresso como (?<!^). Por razões que não posso compreender o seu negativo olhar em frente (?!^)parece funcionar, também ...
Apteryx
7
Isso não lida bem com sublinhados pré-existentes: "Camel2WARNING_Case_CASE"torna - se "camel2_warning_case__case". Você pode adicionar um (?<!_)lookbehind negativo, para resolvê-lo: re.sub('((?<=[a-z0-9])[A-Z]|(?!^)(?<!_)[A-Z](?=[a-z]))', r'_\1', "Camel2WARNING_Case_CASE").lower() retornos 'camel2_warning_case_case'
luckydonald
@Apteryx Você está certo, (?!^)foi chamado incorretamente de "olhar para trás" e deveria ter sido chamado de afirmação negativa à vista . Como essa boa explicação mostra, os lookaheads negativos geralmente vêm após a expressão que você está procurando. Então você pode pensar (?!^)em "encontrar o ''que <start of string>não segue". De fato, um olhar negativo por trás também funciona: você pode pensar (?<!^)em "encontrar ''onde <start of string>não precede".
Nathaniel Jones
17

stringcase é minha biblioteca preferida para isso; por exemplo:

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

Pessoalmente, não tenho certeza de como algo usando expressões regulares em python pode ser descrito como elegante. A maioria das respostas aqui estão apenas fazendo truques do tipo "código golfe" RE. A codificação elegante deve ser facilmente entendida.

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'
TehTris
fonte
11
+=nas cordas é quase sempre uma má ideia. Anexar a uma lista e ''.join()no final. Ou, neste caso, simplesmente juntá-lo com um sublinhado ...
ThiefMaster
21
Como uma expressão regular de linha única não é inatamente superior em quase todos os aspectos práticos (incluindo legibilidade) à iteração ineficiente de caracteres de múltiplas linhas e munging de strings de força bruta? O Python fornece suporte à expressão regular pronto para uso por um motivo.
Cecil Curry
11
@CecilCurry - Expressões regulares são MUITO complexas. Veja o compilador e o analisador que o Python usa: svn.python.org/projects/python/trunk/Lib/sre_compile.py & svn.python.org/projects/python/trunk/Lib/sre_parse.py - manipulação simples de strings como provavelmente muito mais rápido que um ER fazendo o mesmo.
Evan Borgstrom 27/02
11
+1. Regexes podem ser um verdadeiro coletor de CPU e, em cálculos intensivos, reduzirão drasticamente suas performances. Para tarefas simples, sempre prefira funções simples.
Fabien
4
"Para tarefas simples, sempre prefira funções simples" é definitivamente um bom conselho, mas essa resposta não é uma função simples nem elegante. Regex pode ser mais lenta, mas falta a uma função complicada como esta (que também é testado e tem inúmeros pontos potenciais de erro) é a otimização completamente prematura
kevlarr
9

Prefiro evitar, rese possível:

def to_camelcase(s):
    return ''.join(['_' + c.lower() if c.isupper() else c for c in s]).lstrip('_')
>>> to_camelcase("ThisStringIsCamelCase")
'this_string_is_camel_case'
colidir
fonte
11
Este é o mais compacto que evita usar a rebiblioteca e fazer as coisas apenas em uma linha usando apenas str.methods embutidos! É semelhante a essa resposta , mas evita o uso de fatias e adicionais if ... elsesimplesmente removendo "_" potencialmente adicionado como primeiro caractere. Eu gosto mais disso.
colidyre 7/03
Para uma resposta aceita, 6.81 µs ± 22.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)mas para essa resposta 2.51 µs ± 25.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)que é 2,5 vezes mais rápida! Amo isso!
WBAR 20/03
8
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
Jimmy
fonte
7

Eu acho que essa solução é mais direta que as respostas anteriores:

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

Quais saídas:

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 expressão regular corresponde a três padrões:

  1. [A-Z]?[a-z]+: Letras minúsculas consecutivas que, opcionalmente, começam com uma letra maiúscula.
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): Duas ou mais letras maiúsculas consecutivas. Ele usa um lookahead para excluir a última letra maiúscula, se for seguida por uma letra minúscula.
  3. \d+: Números consecutivos.

Ao usar re.findall, obtemos uma lista de "palavras" individuais que podem ser convertidas em minúsculas e associadas a sublinhados.

rspeed
fonte
11
Há um bom exemplo aqui para obter o token numérico de forma independente.
math_law
11
Quebrado: convert ("aB") -> 'a'
adw
5

Não sei por que usar as duas chamadas .sub ()? :) Eu não sou um guru de regex, mas simplifiquei a função para este, que é adequada para minhas necessidades, só precisava de uma solução para converter camelCasedVars da solicitação POST em vars_with_underscore:

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

Ele não funciona com nomes como getHTTPResponse, porque ouvi dizer que é uma má convenção de nomenclatura (deve ser como getHttpResponse, é óbvio que é muito mais fácil memorizar este formulário).

desesper4do
fonte
Esqueci de mencionar que '{1}' não é necessário, mas às vezes isso ajuda a esclarecer alguma névoa.
desper4do
2
-1: isso simplesmente não funciona. Tente por exemplo, com 'HTTPConnectionFactory', seu código produz 'h_tt_pconnection_factory', código de resposta aceita produz'http_connection_factory'
vartec
4

Aqui está a minha solução:

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)

Ele suporta os casos de canto discutidos nos comentários. Por exemplo, ele será convertido getHTTPResponseCodepara o get_http_response_codeque deveria.

Evan Fosmark
fonte
7
-1 porque isso é muito complicado comparado ao uso de regexps.
Eric O Lebigot
7
EOL, tenho certeza de que muitas pessoas que não são da regexp pensariam o contrário.
Evan Fosmark
Essa solução falha nesses casos: _Method, _test_Method , __test__Method, getHTTPrespnseCode, __get_HTTPresponseCode, _Camel_Case, _Test e _test_Method.
18711 freegnu
3
@Evan, essas pessoas seriam maus programadores.
Jesse Dhillon
3

Para se divertir:

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

Ou, mais pela diversão:

>>> 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'
gahooa
fonte
3
c.isupper () em vez de c no ABCEF ... Z
Jimmy
11
Python não tem regexes? Um rápido \ / az] \ K ([AZ] [az]) / _ \ L $ 1 / g; lc $ _' em Perl faz o trabalho (apesar de não lidar bem getHTTPResponseCode; mas que é esperado, que deve ser nomeado getHttpResponseCode)
jrockway
5
str.joinfoi descontinuado por idades . Use em ''.join(..)vez disso.
John Fouhy 24/07/2009
jrockway: Possui expressões regulares, através do módulo "re". Não deve ser muito difícil fazer esse trabalho usando regex, em vez das abordagens postadas aqui.
Matthew Iselin
Python noob aqui, mas por que retornar str.join ('', output)? Apenas para criar uma cópia?
Tarks 24/07/2009
3

O uso de expressões regulares pode ser o mais curto, mas esta solução é muito mais legível:

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
3k-
fonte
@ blueyed que é completamente independente, esta questão não tem nada a ver com django.
3k-
É apenas um exemplo, como HTTPResponseCode, tratado por stackoverflow.com/a/23561109/15690 .
blueyed
3

Tantos métodos complicados ... Basta encontrar todo o grupo "Titled" e associar sua variante mais baixa com sublinhado.

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

Se você não deseja criar números como o primeiro caractere do grupo ou o grupo separado - pode usar a ([A-z][a-z0-9]*)máscara.

unitto
fonte
2

Não está na biblioteca padrão, mas encontrei este script que parece conter a funcionalidade necessária.

Stefano Borini
fonte
2

Este não é um método elegante, é uma implementação de 'nível muito baixo' de uma máquina de estado simples (máquina de estado de campo de bits), possivelmente o modo mais anti-pitonico para resolver isso, no entanto, o módulo reimplementar também implementa uma máquina de estado muito complexa para resolver essa questão simples. tarefa, então eu acho que esta é uma boa solução.

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 pode analisar todos os tipos de caso: UpperSEQUENCEInterleaved, under_score, BIG_SYMBOLS e cammelCasedMethods

Espero que seja útil

jdavidls
fonte
11
Hediondo, mas roda cerca de 3x mais rápido que o método regex na minha máquina. :)
jdiaz5513
1

Levemente adaptado de https://stackoverflow.com/users/267781/matth que usa geradores.

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()
Salvatore
fonte
1

Dê uma olhada na excelente biblioteca de Esquemas

https://github.com/schematics/schematics

Ele permite que você crie estruturas de dados digitados que podem serializar / desserializar do tipo python para Javascript, por exemplo:

class MapPrice(Model):
    price_before_vat = DecimalType(serialized_name='priceBeforeVat')
    vat_rate = DecimalType(serialized_name='vatRate')
    vat = DecimalType()
    total_price = DecimalType(serialized_name='totalPrice')
Caçador de Iain
fonte
1

Este método simples deve fazer o trabalho:

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()
  • Procuramos letras maiúsculas precedidas por qualquer número de (ou zero) letras maiúsculas e seguidas por qualquer número de caracteres minúsculos.
  • Um sublinhado é colocado logo antes da ocorrência da última letra maiúscula encontrada no grupo, e um pode ser colocado antes dessa letra maiúscula, caso seja precedido por outras letras maiúsculas.
  • Se houver sublinhados à direita, remova-os.
  • Finalmente, toda a sequência de resultados é alterada para minúscula.

(extraído daqui , veja o exemplo de trabalho online )

Mathieu Rodic
fonte
Esta é uma resposta para a pergunta oposta (como converter para camel case).
Justin
1

Uau, acabei de roubar isso de trechos de django. ref http://djangosnippets.org/snippets/585/

Bastante elegante

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

Exemplo:

camelcase_to_underscore('ThisUser')

Devoluções:

'this_user'

REGEX DEMO

brianray
fonte
11
Forma incorreta usando str como um nome de variável local.
freegnu
Isso falha miseravelmente se houver sublinhados no início ou no final de uma string e se houver sublinhados antes de uma letra maiúscula.
freegnu
take doesnt em números de conta 😬
villy393
Às vezes, não é uma boa ideia roubar código.
colidyre 7/03
0

Um exemplo horrendo usando expressões regulares (você pode facilmente limpar isso :)):

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

Funciona para getHTTPResponseCode!

Como alternativa, usando lambda:

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: Também deve ser bastante fácil ver que há espaço para melhorias em casos como "Teste", porque o sublinhado é inserido incondicionalmente.

Matthew Iselin
fonte
0

Aqui está algo que eu fiz para alterar os cabeçalhos em um arquivo delimitado por tabulações. Estou omitindo a parte em que apenas editei a primeira linha do arquivo. Você pode adaptá-lo facilmente ao Python com a re library. Isso também inclui separar números (mas mantém os dígitos juntos). Fiz isso em duas etapas, porque era mais fácil do que dizer para não colocar um sublinhado no início de uma linha ou tabulação.

Etapa 1 ... encontre letras maiúsculas ou números inteiros precedidos por letras minúsculas e preceda-os com um sublinhado:

Procurar:

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

Substituição:

\1_\l\2/

Etapa 2 ... execute o procedimento acima e execute-o novamente para converter todos os limites em minúsculas:

Procurar:

([A-Z])

Substituição (barra invertida, L minúsculo, barra invertida, uma):

\l\1
Joe Tricarico
fonte
0

Eu estava procurando uma solução para o mesmo problema, exceto que precisava de uma corrente; por exemplo

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

Começando pelas boas soluções de duas palavras aqui, criei o seguinte:

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

A maior parte da lógica complicada é evitar minúsculas na primeira palavra. Aqui está uma versão mais simples, se você não se importa de alterar a primeira palavra:

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

Obviamente, você pode pré-compilar as expressões regulares ou juntar-se ao sublinhado em vez do hífen, conforme discutido nas outras soluções.

Jim Pivarski
fonte
0

Conciso sem expressões regulares, mas HTTPResponseCode => activationpresponse_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())])
Dantalion
fonte
0

Sem nenhuma biblioteca:

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('__','_')

Um pouco pesado, mas

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

RegEx muito bom proposto neste site :

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

Se o python tiver um método String Split, ele deve funcionar ...

Em Java:

String s = "loremIpsum";
words = s.split("(?&#60;!^)(?=[A-Z])");
Jmini
fonte
Infelizmente, o módulo de expressão regular do Python (na versão 3.6) não suporta divisão em correspondências de tamanho zero.
usar o seguinte comando
0
def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() else '') + y, 
        name
    ).lower()

E se precisarmos cobrir um caso com entrada já não camelada:

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

Caso alguém precise transformar um arquivo de origem completo, aqui está um script que fará isso.

# 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))
Pascal T.
fonte
-1

Eu tive muita sorte com este:

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)

Isso pode, obviamente, ser otimizado para velocidade de um minúsculo pouco se você quiser.

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)
codekoala
fonte
-1
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()
JohnBoy
fonte
-3

Usar: str.capitalize() para converter a primeira letra da string (contida na variável str) em uma letra maiúscula e retorna a string inteira.

Exemplo: Comando: "olá" .capitalize () Saída: Olá

Arshin
fonte
Isso não está relacionado à questão - o OP quer CamelCase -> snake_case, não Capitalização.
Brad Koch