2010-11-29 16 views
27

He escrito la función siguiente que convierte subrayado en camelcase con la primera palabra en minúscula, es decir, "get_this_value" -> "getThisValue". También tengo requisito para preservar ataque y de salida de subrayado y también doble (triple etc.) pone de relieve, en su caso, es decir,¿Cómo puedo simplificar esta conversión de subrayado a camelcase en Python?

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

me siento el código anterior como está escrito en estilo no-Pythonic, aunque funciona como se esperaba, por lo que buscamos cómo simplificar el código y escribirlo usando listas de comprensión, etc.

+0

su pregunta no está clara; ¿Desea convertir "get_this_value" en "getThisValue" o "_get_ThisValue_" – tuergeist

+0

"get_this_value" -> "getThisValue", la primera variante. Sin guiones bajos a menos que estén al principio, al final o hay dos o más guiones bajos que se siguen uno al otro. –

+0

¿Estás seguro de que tu función funciona como se esperaba? Estás diciendo que quieres * preservar * dobles, triples ... guiones bajos, sin embargo, lo que hace tu código es cortar un guión bajo. Es decir. de get__this_value, obtienes get_ThisValue y de get____this, get___This. ¿Es esto lo que quieres? – ecik

Respuesta

26

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

Una opción para arreglar esto es un generador. Podemos hacer fácilmente que esto devuelva una cosa para la primera entrada y otra para todas las entradas posteriores. 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 the conditional operator para que podamos manejar las entradas en blanco devueltas por dos guiones bajos dentro de una lista de comprensión.

Así que si tenemos una palabra que llamamos al generador para obtener la función que utilizará para establecer el caso, y si no lo hacemos simplemente usamos _ dejando el generador intacto.

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("_")) 
+0

Bonito pero FWIW str.lower y str.capitalize no requieren una importación. – freegnu

+0

@freegnu - Gracias. Han actualizado el código para usar 'str.lower' y' str.capitalize'. –

+3

El uso de 'str' hace que no funcione con objetos Unicode. Intente reemplazar 'str' con' tipo (valor) '. –

2

Creo que el código es correcto. Tienes una especificación bastante compleja, así que si insistes en aplastarla en el lecho de Procrustean de una lista comprensiva, entonces es probable que dañes la claridad del código.

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

  1. Para utilizar el método join para construir el resultado en O (n ) espacio y el tiempo, en lugar de las aplicaciones repetidas de += que es O (n ²).
  2. Para agregar un docstring.

De esta manera:

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 yo consideraría hacer sería memoize la función. Supongo que está traduciendo automáticamente el código fuente de alguna manera, y espera que aparezcan los mismos nombres muchas veces. Así que también podría almacenar la conversión en lugar de volver a calcularla cada vez. Una manera fácil de hacerlo sería usar el decorador @memoized del Python decorator library.

2

Estoy de acuerdo con Gareth en que el código es correcto. Sin embargo, si realmente desea un enfoque más corta, sin embargo, usted podría intentar leer 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) 
0

Ésta es la forma más compacta para hacerlo:

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

¡Esto no cumple con las especificaciones! (Para '_get_this_value' devuelve' GetThisValue' mientras que '_getThisValue' es necesario.) –

0

Por el bien de expresiones regulares!

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] 
+0

¡No cumple con las especificaciones! Para 'get__this_value' devuelve' getThisValue' mientras que 'get_ThisValue' es deseado. –

+0

@ gareth-rees tienes razón, he editado, debe estar bien ahora. –

+1

¡Todavía no está bien! Para 'foo' devuelve' foo_'! –

2

El problema requiere una función que devuelve una palabra en minúscula la primera vez, pero después palabras en mayúscula. Puede hacerlo con una cláusula if, pero luego se debe evaluar 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 tantas if 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)) 
0

Otra solución 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'(_*[A-Z])', 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 podría fallar por nombres containting dígitos - Depende de cómo los capitalizarías.

+0

Está devolviendo los resultados incorrectos. – freegnu

+0

@freegnu: ¿Podría ser más preciso? –

+0

@ 'Oben Sonne' Aunque el ejemplo dado por el solicitante deja de subrayar consecutivamente, la especificación solicitada dice que se deben conservar los guiones bajos dobles y triples. El ejemplo dado por el solicitante probablemente sea incorrecto. – freegnu

1

Aquí hay una lista de expresión del generador del estilo 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) 
0

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

Esta funciona a excepción de salir de la primera palabra en minúscula.

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

(Sé que esto no es exactamente lo que usted pidió, y este tema es bastante antiguo, pero ya que es muy importante en la búsqueda de este tipo de conversiones en Google pensé que me gustaría añadir mi solución en caso de que ayuda a alguien más).

+3

Esto es excelente. Para el usuario novato de Python, si tiene palabras separadas por cualquier símbolo (en mi caso, un espacio) puede reemplazar el '_' en ambas ubicaciones para usar esta funcionalidad para darle camel case en otras situaciones. +1 – BlackVegetable

+2

'return word.split ('_') [0] + '' .join (x.capitalize() o '_' para x en word.split ('_') [1:)' –

14

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) 

Mediante pruebas unutbu 's:

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

Esto es una buena solución, ya que no estropea las cuerdas que ya están en camel case. – ishmael

2

Este algoritmo funciona bien con el dígito:

import re 

PATTERN = re.compile(r''' 
    (?<!\A) # not at the start of the string 
    _ 
    (?=[a-zA-Z]) # followed by a letter 
    ''', re.X) 

def camelize(value): 
    tokens = PATTERN.split(value) 
    response = tokens.pop(0).lower() 
    for remain in tokens: 
     response += remain.capitalize() 
    return response 

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

voto adicional para los grandes casos de prueba. –

6

Aquí está uno más simple. Puede que no sea perfecto para todas las situaciones, pero cumple mis requisitos, ya que solo estoy convirtiendo las variables de Python, que tienen un formato específico, en camel-case. Esto capitaliza 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('_'))) 
1

Aquí está el mío, confiando principalmente en la lista de comprensión, dividir y unir.Además parámetro opcional para utilizar diferentes delimitador:

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

Además, para mayor abundamiento, incluyendo lo que se hizo referencia anteriormente como solución a partir de otra pregunta como el reverso (no es mi propio código, basta con repetir para una fácil referencia):

first_cap_re = re.compile('(.)([A-Z][a-z]+)') 
all_cap_re = re.compile('([a-z0-9])([A-Z])') 
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() 
Cuestiones relacionadas