¿Cómo puedo simplificar esta conversión de guión bajo a camelcase en Python?

He escrito la siguiente función que convierte el subrayado en camelcase con la primera palabra en minúsculas, es decir, “get_this_value” -> “getThisValue”. También tengo el requisito de conservar los guiones bajos iniciales y finales y también los guiones bajos dobles (triples, etc.), si los hay, es decir,

"_get__this_value_" -> "_get_ThisValue_". 

El código:

 def underscore_to_camelcase(value): output = "" first_word_passed = False for word in value.split("_"): if not word: output += "_" continue if first_word_passed: output += word.capitalize() else: output += word.lower() first_word_passed = True return output 

Estoy sintiendo el código anterior como está escrito en un estilo no Pythonic, aunque funciona como se esperaba, así que busco cómo simplificar el código y escribirlo usando listas de comprensión, etc.

Tu código está bien. El problema que creo que estás tratando de resolver es que if first_word_passed ve un poco feo.

Una opción para arreglar esto es un generador. Podemos hacer que este retorno sea una cosa para la primera entrada y otra para todas las entradas subsiguientes. Como Python tiene funciones de primera clase, podemos hacer que el generador devuelva la función que queremos usar para procesar cada palabra.

Luego, solo necesitamos usar el operador condicional para poder manejar las entradas en blanco devueltas por dos guiones bajos dentro de una lista de comprensión.

Entonces, si tenemos una palabra, llamamos al generador para obtener la función que se usará para establecer el caso, y si no lo hacemos, simplemente usamos _ dejando el generador sin tocar.

 def underscore_to_camelcase(value): def camelcase(): yield str.lower while True: yield str.capitalize c = camelcase() return "".join(c.next()(x) if x else '_' for x in value.split("_")) 

Este funciona excepto para dejar la primera palabra como minúscula.

 def convert(word): return ''.join(x.capitalize() or '_' for x in word.split('_')) 

(Sé que esto no es exactamente lo que pediste, y este hilo es bastante antiguo, pero como es bastante importante al buscar este tipo de conversiones en Google, pensé que agregaría mi solución en caso de que ayude a alguien más).

Prefiero una expresión regular, personalmente. Aquí hay uno que está haciendo el truco para mí:

 import re def to_camelcase(s): return re.sub(r'(?!^)_([a-zA-Z])', lambda m: m.group(1).upper(), s) 

Usando las pruebas de unutbu :

 tests = [('get__this_value', 'get_ThisValue'), ('_get__this_value', '_get_ThisValue'), ('_get__this_value_', '_get_ThisValue_'), ('get_this_value', 'getThisValue'), ('get__this__value', 'get_This_Value')] for test, expected in tests: assert to_camelcase(test) == expected 

Aquí hay uno más simple. Puede que no sea perfecto para todas las situaciones, pero cumple con mis requisitos, ya que simplemente estoy convirtiendo las variables de Python, que tienen un formato específico, a camel-case. Esto hace mayúscula todo menos la primera palabra.

 def underscore_to_camelcase(text): """ Converts underscore_delimited_text to camelCase. Useful for JSON output """ return ''.join(word.title() if i else word for i, word in enumerate(text.split('_'))) 

Creo que el código está bien. Tiene una especificación bastante compleja, por lo que si insiste en aplastarla en el lecho de Procrustean de una lista de comprensión, entonces es probable que dañe la claridad del código.

Los únicos cambios que haría serían:

  1. Para usar el método de join para generar el resultado en O ( n ) espacio y tiempo, en lugar de aplicaciones repetidas de += que es O ( n ²).
  2. Para añadir una cadena de documentación.

Me gusta esto:

 def underscore_to_camelcase(s): """Take the underscore-separated string s and return a camelCase equivalent. Initial and final underscores are preserved, and medial pairs of underscores are turned into a single underscore.""" def camelcase_words(words): first_word_passed = False for word in words: if not word: yield "_" continue if first_word_passed: yield word.capitalize() else: yield word.lower() first_word_passed = True return ''.join(camelcase_words(s.split('_'))) 

Dependiendo de la aplicación, otro cambio que consideraría realizar sería memorizar la función. Supongo que usted está traduciendo automáticamente el código fuente de alguna manera, y espera que los mismos nombres aparezcan muchas veces. Por lo tanto, también puede almacenar la conversión en lugar de volver a calcularla cada vez. Una forma fácil de hacerlo sería utilizar el decorador @memoized de la biblioteca de decoradores de Python .

Este algoritmo se desempeña bien con dígitos:

 import re PATTERN = re.compile(r''' (? 

Ejemplos:

 >>> camelize('Foo') 'foo' >>> camelize('_Foo') '_foo' >>> camelize('Foo_') 'foo_' >>> camelize('Foo_Bar') 'fooBar' >>> camelize('Foo__Bar') 'foo_Bar' >>> camelize('9') '9' >>> camelize('9_foo') '9Foo' >>> camelize('foo_9') 'foo_9' >>> camelize('foo_9_bar') 'foo_9Bar' >>> camelize('foo__9__bar') 'foo__9_Bar' 

Estoy de acuerdo con Gareth en que el código está bien. Sin embargo, si realmente desea un enfoque más corto pero legible, podría intentar algo como esto:

 def underscore_to_camelcase(value): # Make a list of capitalized words and underscores to be preserved capitalized_words = [w.capitalize() if w else '_' for w in value.split('_')] # Convert the first word to lowercase for i, word in enumerate(capitalized_words): if word != '_': capitalized_words[i] = word.lower() break # Join all words to a single string and return it return "".join(capitalized_words) 

El problema requiere una función que devuelva una palabra en minúscula la primera vez, pero luego las palabras en mayúscula. Puede hacer eso con una cláusula if , pero luego debe evaluarse la cláusula if para cada palabra. Una alternativa atractiva es usar un generador. Puede devolver una cosa en la primera llamada, y otra cosa en llamadas sucesivas, y no requiere tantos como s.

 def lower_camelcase(seq): it=iter(seq) for word in it: yield word.lower() if word.isalnum(): break for word in it: yield word.capitalize() def underscore_to_camelcase(text): return ''.join(lower_camelcase(word if word else '_' for word in text.split('_'))) 

Aquí hay un código de prueba para demostrar que funciona:

 tests=[('get__this_value','get_ThisValue'), ('_get__this_value','_get_ThisValue'), ('_get__this_value_','_get_ThisValue_'), ('get_this_value','getThisValue'), ('get__this__value','get_This_Value'), ] for test,answer in tests: result=underscore_to_camelcase(test) try: assert result==answer except AssertionError: print('{r!r} != {a!r}'.format(r=result,a=answer)) 

Aquí hay una lista de expresiones de generador de estilos de comprensión.

 from itertools import count def underscore_to_camelcase(value): words = value.split('_') counter = count() return ''.join('_' if w == '' else w.capitalize() if counter.next() else w for w in words ) 

Aquí está el mío, confiando principalmente en la comprensión de la lista, la división y la unión. Parámetro opcional más para usar diferentes delimitadores:

 def underscore_to_camel(in_str, delim="_"): chunks = in_str.split(delim) chunks[1:] = [_.title() for _ in chunks[1:]] return "".join(chunks) 

Además, por razones de integridad, incluido lo que se mencionó anteriormente como solución de otra pregunta como al revés (NO mi propio código, solo se repite para una referencia fácil):

 first_cap_re = re.compile('(.)([AZ][az]+)') all_cap_re = re.compile('([a-z0-9])([AZ])') def camel_to_underscore(in_str): s1 = first_cap_re.sub(r'\1_\2', name) return all_cap_re.sub(r'\1_\2', s1).lower() 

Esta es la forma más compacta de hacerlo:

 def underscore_to_camelcase(value): words = [word.capitalize() for word in value.split('_')] words[0]=words[0].lower() return "".join(words) 

Otra solución de expresión regular:

 import re def conv(s): """Convert underscore-separated strings to camelCase equivalents. >>> conv('get') 'get' >>> conv('_get') '_get' >>> conv('get_this_value') 'getThisValue' >>> conv('__get__this_value_') '_get_ThisValue_' >>> conv('_get__this_value__') '_get_ThisValue_' >>> conv('___get_this_value') '_getThisValue' """ # convert case: s = re.sub(r'(_*[AZ])', lambda m: m.group(1).lower(), s.title(), count=1) # remove/normalize underscores: s = re.sub(r'__+|^_+|_+$', '|', s).replace('_', '').replace('|', '_') return s if __name__ == "__main__": import doctest doctest.testmod() 

Funciona para sus ejemplos, pero puede fallar para los nombres que contienen dígitos; depende de cómo los capitalizaría.

Para regexp sake!

 import re def underscore_to_camelcase(value): def rep(m): if m.group(1) != None: return m.group(2) + m.group(3).lower() + '_' else: return m.group(3).capitalize() ret, nb_repl = re.subn(r'(^)?(_*)([a-zA-Z]+)', rep, value) return ret if (nb_repl > 1) else ret[:-1] 

Una versión ligeramente modificada:

 import re def underscore_to_camelcase(value): first = True res = [] for u,w in re.findall('([_]*)([^_]*)',value): if first: res.append(u+w) first = False elif len(w)==0: # trailing underscores res.append(u) else: # trim an underscore and capitalize res.append(u[:-1] + w.title()) return ''.join(res) 

Sé que esto ya ha sido respondido, pero se me ocurrió un poco de azúcar sintáctica que maneja un caso especial que la respuesta seleccionada no tiene (las palabras con dunders en ellos es decir “my_word__is_____ugly” a “myWordIsUgly”). Obviamente, esto se puede dividir en varias líneas, pero me gustó el desafío de hacerlo. Agregué saltos de línea para mayor claridad.

 def underscore_to_camel(in_string): return "".join( list( map( lambda index_word: index_word[1].lower() if index_word[0] == 0 else index_word[1][0].upper() + (index_word[1][1:] if len(index_word[1]) > 0 else ""), list(enumerate(re.split(re.compile(r"_+"), in_string) ) ) ) ) )