Elegante función de Python para convertir CamelCase a snake_case?

333

Ejemplo:

>>> convert('CamelCase')
'camel_case'
Sridhar Ratnakumar
fuente
28
Para convertir en la otra dirección, vea esta otra pregunta de stackoverflow.
Nathan
10
nb eso es NotCamelCaseperothisIs
Matt Richards
55
@MattRichards Es un tema de disputa. wiki
NO_NAME
@MattRichards Por ejemplo, en Java usan ambos, CamelCase se usa para nombrar definiciones de Clase, mientras que camelCase se usa para nombrar variables inicializadas.
darkless

Respuestas:

798

Camel caso a serpiente

import re

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

Si hace esto muchas veces y lo anterior es lento, compile la expresión regular de antemano:

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

Para manejar casos más avanzados especialmente (esto ya no es reversible):

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 de serpiente a caso de camello

name = 'snake_case_name'
name = ''.join(word.title() for word in name.split('_'))
print(name)  # SnakeCaseName
publicación
fuente
1
Esta solución falla en estos casos: _test_Method, __test__Method, _Test, getHTTPresponseCode, __CamelCase y _Camel_Case.
freegnu
66
¿Qué tal el reverso? Convierte not_camel_casea notCamelCasey / o NotCamelCase?
juan2x
99
Para evitar dobles guiones bajos al convertir, por ejemplo, camel_Case, agregue esta línea:s2.replace('__', '_')
Marcus Ahlberg
2
Tenga en cuenta que esto no es muy reversible. getHTTPResponseCode debería convertirse en get_h_t_t_p_response_code. getHttpResponseCode debería convertirse a get_http_response_code
K2xL
44
@AnmolSinghJaggi La primera expresión regular maneja el caso de borde de un acrónimo seguido de otra palabra (por ejemplo, "HTTPResponse" -> "HTTP_Response") O el caso más normal de una palabra inicial en minúscula seguida de una palabra en mayúscula (por ejemplo, "getResponse" -> " get_Response ". La segunda expresión regular maneja el caso normal de dos no acrónimos (por ejemplo," ResponseCode "->" Response_Code ") seguido de una última llamada para poner todo en minúsculas. Por lo tanto," getHTTPResponseCode "->" getHTTP_ResponseCode "->" get_HTTP_Response_Code "- > "get_http_response_code"
Jeff Moser
188

Hay una biblioteca de inflexiones en el índice del paquete que puede manejar estas cosas por usted. En este caso, estarías buscando inflection.underscore():

>>> inflection.underscore('CamelCase')
'camel_case'
Brad Koch
fuente
44
No entiendo por qué las personas votan por el uso de funciones personalizadas cuando hay una gran biblioteca que realiza esta tarea. No deberíamos reinventar la rueda.
oden
88
@oden ¿Quizás porque agregar una dependencia completamente nueva para hacer el trabajo de una función de una sola línea es frágil?
Cecil Curry
11
Por un lado, seguro que es exagerado. En una aplicación más grande, no es necesario reinventar y ofuscar la rueda.
Brad Koch
11
Las expresiones regulares retroceden mucho en una "línea única", por lo que es mucho más de una línea con las pruebas adecuadas.
studgeek
12
@CecilCurry: Estoy seguro de que eres un gran programador, pero no estoy seguro de que no haya casos que no hayas considerado, solo mira otras respuestas aquí para ver ejemplos. Es por eso que siempre elegiré una biblioteca, porque es la experiencia total de muchos más desarrolladores que solo yo.
Michael Scheper
105

No sé por qué todo esto es tan complicado.

para la mayoría de los casos, la expresión simple ([A-Z]+)hará el truco

>>> 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 el primer personaje simplemente agrega mirar detrás (?!^)

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

Si desea separar ALLCaps a all_caps y espera números en su cadena, todavía no necesita hacer dos ejecuciones separadas, solo use |Esta expresión ((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))puede manejar casi todos los escenarios del libro

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

Todo depende de lo que desee, así que use la solución que mejor se adapte a sus necesidades, ya que no debería ser demasiado complicado.

nJoy!

nickl-
fuente
1
La última iteración es la más inteligente, IMO. Me tomó un poco entender que solo está reemplazando el carácter único al comienzo de cada palabra, y eso fue solo porque el enfoque era diferente al que yo había ideado. Bien hecho.
Justin Miller
2
Me sorprendió la (?!^)expresión que se llamaba mirar hacia atrás. A menos que me falte algo, lo que realmente queremos aquí es una mirada negativa que debe expresarse como (?<!^). Por razones que no entiendo, su mirada negativa hacia adelante (?!^)parece funcionar también ...
Apteryx
77
Esto no maneja bien los guiones bajos preexistentes: se "Camel2WARNING_Case_CASE"convierte en "camel2_warning_case__case". Puede agregar una (?<!_)mirada negativa hacia atrás, para resolverlo: re.sub('((?<=[a-z0-9])[A-Z]|(?!^)(?<!_)[A-Z](?=[a-z]))', r'_\1', "Camel2WARNING_Case_CASE").lower() retornos 'camel2_warning_case_case'
luckydonald
@Apteryx Tienes razón, (?!^)se llamó incorrectamente "mirar hacia atrás" y en su lugar debería haberse llamado afirmación negativa anticipada . Como muestra esta buena explicación , las miradas negativas generalmente aparecen después de la expresión que estás buscando. Por lo tanto, puede pensar (?!^)en "encontrar ''donde <start of string>no sigue". De hecho, una mirada hacia atrás negativa también funciona: se puede pensar (?<!^)como "buscar ''donde <start of string>no precede".
Nathaniel Jones el
18

stringcase es mi biblioteca de acceso para esto; p.ej:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'
Galán
fuente
11

Prefiero evitar resi es posible:

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'
colidire
fuente
1
¡Este es el más compacto que evita usar la rebiblioteca y hacer las cosas solo en una línea usando métodos str. incorporados! Es similar a esta respuesta , pero evita el uso de segmentación y adicional if ... elsesimplemente quitando "_" potencialmente agregado como primer carácter. Esto me gusta más
colidyre
¡Para una respuesta aceptada 6.81 µs ± 22.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)pero para esta respuesta 2.51 µs ± 25.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)que es 2.5 veces más rápida! ¡Me gusta esto!
WBAR
10

Personalmente, no estoy seguro de cómo algo que usa expresiones regulares en Python puede describirse como elegante. La mayoría de las respuestas aquí solo están haciendo trucos RE de "código de golf". Se supone que la codificación elegante se entiende fácilmente.

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
fuente
1
+=en cadenas es casi siempre una mala idea. Añadir a una lista y ''.join()al final. O en este caso, simplemente
únala
22
¿Cómo es que una expresión regular de una sola línea no es innatamente superior en casi todas las formas prácticas (incluida la legibilidad) a la ineficiente iteración de caracteres de varias líneas y la combinación de cadenas de fuerza bruta? Python proporciona soporte de expresiones regulares listo para usar por una razón.
Cecil Curry
1
@CecilCurry: las expresiones regulares son MUY complejas. Consulte el compilador y el analizador que usa Python: svn.python.org/projects/python/trunk/Lib/sre_compile.py & svn.python.org/projects/python/trunk/Lib/sre_parse.py - Simple manipulación de cadenas como esto probablemente mucho más rápido que un RE haciendo lo mismo.
Evan Borgstrom
1
+1. Las expresiones regulares pueden ser un sumidero real de la CPU, y en cálculos intensivos disminuirán drásticamente sus rendimientos. Para tareas simples, siempre prefiera funciones simples.
Fabien
44
"Para tareas simples, siempre prefiera funciones simples" es definitivamente un buen consejo, pero esta respuesta no es una función simple ni elegante. Regex puede ser más lento, pero el incumplimiento de una función complicada como esta (que TAMBIÉN no se ha probado y tiene numerosos puntos de error potenciales) es una optimización completamente prematura
kevlarr
8
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
Palanqueta
fuente
7

Creo que esta solución es más sencilla que las respuestas 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))

Qué salidas:

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

La expresión regular coincide con tres patrones:

  1. [A-Z]?[a-z]+: Letras minúsculas consecutivas que opcionalmente comienzan con una letra mayúscula.
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): Dos o más letras mayúsculas consecutivas. Utiliza una búsqueda anticipada para excluir la última letra mayúscula si es seguida por una letra minúscula.
  3. \d+: Numeros consecutivos.

Al usarlo re.findall, obtenemos una lista de "palabras" individuales que se pueden convertir a minúsculas y unir con guiones bajos.

rspeed
fuente
1
Aquí hay un buen ejemplo para obtener los tokens numéricos de forma independiente.
math_law
1
Roto: convertir ("aB") -> 'a'
adw
5

No tengo idea de por qué usar ambas llamadas .sub ()? :) No soy regex guru, pero simplifiqué la función para este, que es adecuado para mis necesidades, solo necesitaba una solución para convertir camelCasedVars de la solicitud POST a vars_with_underscore:

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

No funciona con nombres como getHTTPResponse, porque escuché que es una convención de nomenclatura incorrecta (debería ser como getHttpResponse, obviamente, es mucho más fácil memorizar este formulario).

desper4do
fuente
Olvidé mencionar que '{1}' no es necesario, pero a veces ayuda a aclarar un poco de niebla.
desper4do
2
-1: esto simplemente no funciona. Pruebe, por ejemplo 'HTTPConnectionFactory', con , su código produce 'h_tt_pconnection_factory', el código de la respuesta aceptada produce'http_connection_factory'
vartec
4

Aquí está mi solución:

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)

Es compatible con los casos de esquina discutidos en los comentarios. Por ejemplo, se convertirá getHTTPResponseCodea get_http_response_codelo que debería.

rev. Evan Fosmark
fuente
77
-1 porque esto es muy complicado en comparación con el uso de expresiones regulares.
Eric O Lebigot
77
EOL, estoy seguro de que mucha gente no regexp pensaría lo contrario.
Evan Fosmark
Esta solución falla en estos casos: _Method, _test_Method , __test__Method, getHTTPrespnseCode, __get_HTTPresponseCode, _Camel_Case, _Test y _test_Method.
freegnu
3
@Evan, esas personas serían malos programadores.
Jesse Dhillon
3

Por diversión:

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

O, más por el gusto de hacerlo:

>>> 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'
revs gahooa
fuente
3
c.isupper () en lugar de c en ABCEF ... Z
Jimmy
1
Python no tiene expresiones regulares? A rápido 's / [az] \ K ([AZ] [az]) / _ \ L $ 1 / g; lc $ _ 'en Perl hace el trabajo (aunque no maneja bien getHTTPResponseCode; pero eso se espera, eso debería llamarse getHttpResponseCode)
jrockway
55
str.joinha quedado en desuso por siglos . Usar en su ''.join(..)lugar.
John Fouhy el
jrockway: Tiene expresiones regulares, a través del módulo "re". No debería ser demasiado difícil hacer que esto funcione utilizando expresiones regulares en lugar de los enfoques publicados aquí.
Matthew Iselin el
Python noob aquí, pero ¿por qué devolver str.join ('', salida)? ¿Solo para crear una copia?
Tarks
3

El uso de expresiones regulares puede ser el más corto, pero esta solución es mucho más legible:

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-
fuente
@blueyed que no tiene ninguna relación, esta pregunta no tiene nada que ver con django.
3k-
Es solo un ejemplo, como HTTPResponseCode, que maneja stackoverflow.com/a/23561109/15690 .
azulado
3

Tantos métodos complicados ... Simplemente encuentre todo el grupo "Titulado" y únase a su variante en minúsculas con guión bajo.

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

Si no desea crear números como el primer carácter del grupo o un grupo separado, puede usar la ([A-z][a-z0-9]*)máscara.

unitto
fuente
2

No en la biblioteca estándar, pero encontré este script que parece contener la funcionalidad que necesita.

Stefano Borini
fuente
2

Este no es un método elegante, es una implementación de 'nivel bajo' de una máquina de estado simple (máquina de estado de campo de bits), posiblemente el modo más antipitónico para resolver esto, sin embargo, re module también implementa una máquina de estado demasiado compleja para resolver este problema simple. tarea, así que creo que esta es una buena solución.

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 puede analizar todos los tipos de casos: UpperSEQUENCEInterleaved, under_score, BIG_SYMBOLS y cammelCasedMethods

Espero que sea útil

revs jdavidls
fuente
1
Horrible, pero funciona aproximadamente 3 veces más rápido que el método regex en mi máquina. :)
jdiaz5513
1

Ligeramente adaptado de https://stackoverflow.com/users/267781/matth que usan generadores.

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

Echa un vistazo a la excelente lib Schematics

https://github.com/schematics/schematics

Le permite crear estructuras de datos mecanografiados que pueden serializar / deserializar desde Python a Javascript Javascript, por ejemplo:

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

Este método simple debería hacer el trabajo:

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()
  • Buscamos letras mayúsculas precedidas por cualquier número de (o cero) letras mayúsculas, y seguidas por cualquier número de caracteres en minúscula.
  • Se coloca un guión bajo justo antes de que ocurra la última letra mayúscula encontrada en el grupo, y se puede colocar antes de esa letra mayúscula en caso de que esté precedida por otras letras mayúsculas.
  • Si hay guiones bajos, elimínelos.
  • Finalmente, toda la cadena de resultados se cambia a minúsculas.

(tomado de aquí , ver ejemplo de trabajo en línea )

rev. Mathieu Rodic
fuente
Esta es una respuesta para la pregunta opuesta (cómo convertir a camello).
Justin
1

Wow, acabo de robar esto de los fragmentos 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('_')

Ejemplo:

camelcase_to_underscore('ThisUser')

Devoluciones:

'this_user'

DEMO REGEX

brianray
fuente
1
Forma incorrecta usando str como nombre de variable local.
freegnu
Esto falla miserablemente si hay guiones bajos al principio o al final de una cadena y si hay guiones bajos antes de una letra mayúscula.
freegnu
no tiene en cuenta los números 😬
villy393
0

Un ejemplo horrendo que usa expresiones regulares (podría limpiar esto fácilmente :)):

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

Sin embargo, funciona para getHTTPResponseCode.

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

EDITAR: También debería ser bastante fácil ver que hay margen de mejora para casos como "Prueba", porque el guión bajo se inserta incondicionalmente.

Matthew Iselin
fuente
0

Aquí hay algo que hice para cambiar los encabezados en un archivo delimitado por tabulaciones. Estoy omitiendo la parte donde solo edité la primera línea del archivo. Puede adaptarlo a Python con bastante facilidad con la biblioteca re. Esto también incluye separar los números (pero mantiene los dígitos juntos). Lo hice en dos pasos porque era más fácil que decirle que no pusiera un guión bajo al comienzo de una línea o tabulación.

Paso uno ... encuentre letras mayúsculas o enteros precedidos por letras minúsculas y preceda con un guión bajo:

Buscar:

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

Reemplazo:

\1_\l\2/

Paso dos ... toma lo anterior y ejecútalo de nuevo para convertir todas las mayúsculas a minúsculas:

Buscar:

([A-Z])

Reemplazo (eso es barra invertida, L minúscula, barra invertida, una):

\l\1
Joe Tricarico
fuente
0

Estaba buscando una solución al mismo problema, excepto que necesitaba una cadena; p.ej

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

A partir de las buenas soluciones de dos palabras aquí, se me ocurrió lo siguiente:

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

La mayor parte de la lógica complicada es evitar poner en minúscula la primera palabra. Aquí hay una versión más simple si no te importa alterar la primera palabra:

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

Por supuesto, puede precompilar las expresiones regulares o unirse con guión bajo en lugar de guión, como se discutió en las otras soluciones.

Jim Pivarski
fuente
0

Conciso sin expresiones regulares, pero 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())])
Dantalion
fuente
0

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

Un poco pesado, pero

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

Muy agradable RegEx propuesto en este sitio :

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

Si Python tiene un método de división de cadenas, debería funcionar ...

En Java:

String s = "loremIpsum";
words = s.split("(?&#60;!^)(?=[A-Z])");
Jmini
fuente
Desafortunadamente, el módulo de expresión regular de Python no admite (a partir de la versión 3.6) la división en coincidencias de longitud cero.
rspeed
0
def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() else '') + y, 
        name
    ).lower()

Y si necesitamos cubrir un caso con una entrada ya sin camelar:

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

En caso de que alguien necesite transformar un archivo fuente completo, aquí hay un script que lo hará.

# 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.
fuente
-1

He tenido bastante buena suerte con 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)

Obviamente, esto puede ser optimizado para la velocidad de una pequeña poco si así lo desea.

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

Utilizar: str.capitalize() para convertir la primera letra de la cadena (contenida en una cadena variable) a una letra mayúscula y devuelve la cadena completa.

Ejemplo: Comando: "hola" .capitalize () Salida: Hola

Arshin
fuente
Esto no está relacionado con la pregunta: el OP quiere CamelCase -> snake_case, no Capitalización.
Brad Koch