web-dev-qa-db-fra.com

Elégante Python fonction permettant de convertir CamelCase en snake_case?

Exemple:

>>> convert('CamelCase')
'camel_case'
334
Sridhar Ratnakumar

C'est assez complet:

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

Fonctionne avec tout cela (et ne nuit pas aux versions déjà non camelées):

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

Ou si vous voulez l'appeler des zillions de fois, vous pouvez pré-compiler les regexes:

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

N'oubliez pas d'importer le module d'expression régulière

import re
694
epost

Il y a un bibliothèque d'inflexion dans l'index du paquetage qui peut gérer ces choses pour vous. Dans ce cas, vous rechercheriez inflection.underscore() :

>>> inflection.underscore('CamelCase')
'camel_case'
155
Brad Koch

Je ne sais pas pourquoi tout cela est si compliqué.

dans la plupart des cas, l'expression simple ([A-Z]+) fera l'affaire

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

Pour ignorer le premier caractère, ajoutez simplement un regard derrière (?!^)

>>> 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 vous souhaitez séparer ALLCaps en all_caps et que vous attendez des chiffres dans votre chaîne, vous n'avez toujours pas besoin d'effectuer deux exécutions distinctes. Il vous suffit d'utiliser | Cette expression ((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z])) peut gérer à peu près tous les scénarios du livre.

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

Tout dépend de ce que vous voulez, alors utilisez la solution qui vous convient le mieux car elle ne devrait pas être trop compliquée.

nonJoy!

87
nickl-

Personnellement, je ne sais pas comment utiliser des expressions régulières dans python peut être qualifié d'élégant. La plupart des réponses ici ne font que des astuces RE de type "code golf". Le codage élégant est censé être facilement compris.

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

stringcase est ma bibliothèque préférée pour cela; par exemple.:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'
11
Beau
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
8
Jimmy

Je ne comprends pas pourquoi utiliser les deux appels .s ()? :) Je ne suis pas un gourou des expressions rationnelles, mais j'ai simplifié la fonction à celle-ci, qui convient à certains besoins, il me fallait simplement une solution pour convertir camelCasedVars à partir de la demande POST en vars_with_underscore:

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

Cela ne fonctionne pas avec des noms tels que getHTTPResponse, car j’ai entendu dire que c’est une mauvaise convention de nommage (il devrait ressembler à getHttpResponse, c’est évidemment qu’il est beaucoup plus facile de mémoriser ce formulaire).

5
desper4do

Je pense que cette solution est plus simple que les réponses précédentes:

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

Quelles sorties:

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

L'expression régulière correspond à trois modèles:

  1. [A-Z]?[a-z]+: lettres minuscules consécutives commençant éventuellement par une lettre majuscule.
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): deux lettres majuscules ou plus consécutives. Il utilise un lookahead pour exclure la dernière lettre majuscule si elle est suivie d'une lettre minuscule.
  3. \d+: Numéros consécutifs.

En utilisant re.findall, nous obtenons une liste de "mots" individuels qui peuvent être convertis en minuscules et joints à des tirets bas.

5
rspeed

Voici ma solution:

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)

Il prend en charge les affaires discutées dans les commentaires. Par exemple, il convertira getHTTPResponseCode en get_http_response_code comme il se doit.

4
Evan Fosmark

Pour le plaisir:

>>> 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, plus pour le plaisir:

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

Tant de méthodes compliquées ... Il suffit de trouver tous les groupes "Titled" et de joindre sa variante avec casse inférieure.

>>> 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 vous ne voulez pas que les nombres soient comme le premier caractère d'un groupe ou d'un groupe séparé, vous pouvez utiliser le masque ([A-z][a-z0-9]*).

2
unitto

Ce n'est pas une méthode élégante, c'est une implémentation très bas niveau d'une machine à états simple (machine à états de champs de bits), probablement le mode le plus anti-pythonique pour résoudre ce problème, mais le module implémente également une machine à états trop complexe pour résoudre ces problèmes simples. tâche, donc je pense que c'est une bonne solution.

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 peut analyser tous les types de cas: UpperSEQUENCEInterleaved, under_score, BIG_SYMBOLS et cammelCasedMethods

J'espère que cela est utile

2
jdavidls

Pas dans la bibliothèque standard, mais j'ai trouvé ce script qui semble contenir les fonctionnalités dont vous avez besoin.

2
Stefano Borini

Jetez un coup d'œil à l'excellente librairie Schematics

https://github.com/schematics/schematics

Il vous permet de créer des structures de données typées pouvant sérialiser/désérialiser de python à une version Javascript, par exemple:

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

Utiliser des expressions rationnelles est peut-être le plus court, mais cette solution est beaucoup plus lisible:

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
1
3k-

Wow, je viens de voler ceci à Django extraits. ref http://djangosnippets.org/snippets/585/

Assez élégant

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

Exemple:

camelcase_to_underscore('ThisUser')

Résultats:

'this_user'

REGEX DEMO

1
brianray

Je préfère éviter si possible:

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

1
otocan

Légèrement adapté de https://stackoverflow.com/users/267781/matth qui utilisent des générateurs.

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

Voici quelque chose que j'ai fait pour changer les en-têtes d'un fichier délimité par des tabulations. J'omets la partie où je n'ai édité que la première ligne du fichier. Vous pouvez l'adapter à Python assez facilement avec la bibliothèque re. Cela inclut également la séparation des nombres (mais conserve les chiffres ensemble). Je l'ai fait en deux étapes parce que c'était plus facile que de lui dire de ne pas mettre de soulignement au début d'une ligne ou d'une tabulation.

Première étape ... trouvez des lettres majuscules ou des nombres entiers précédés de lettres minuscules et faites-les précéder d'un trait de soulignement:

Chercher:

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

Remplacement:

\1_\l\2/

Deuxième étape ... prenez ce qui précède et exécutez-le à nouveau pour convertir toutes les majuscules en minuscules:

Chercher:

([A-Z])

Remplacement (c'est une barre oblique inverse, une L minuscule, une barre oblique inverse, une):

\l\1
0
Joe Tricarico

Juste au cas où quelqu'un aurait besoin de transformer un fichier source complet, voici un script qui le fera.

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

Très joli RegEx proposé sur ce site :

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

Si python a une méthode String Split, cela devrait fonctionner ...

En Java:

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

Cette méthode simple devrait faire le travail:

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()
  • Nous recherchons des lettres majuscules précédées d’un nombre quelconque de lettres majuscules (ou nulles), suivies d’un nombre quelconque de caractères minuscules.
  • Un trait de soulignement est placé juste avant l'apparition de la dernière lettre majuscule trouvée dans le groupe et peut être placé avant cette lettre majuscule s'il est précédé d'autres lettres majuscules.
  • S'il y a des tirets bas, supprimez-les.
  • Enfin, toute la chaîne de résultat est modifiée en minuscule.

(extrait de ici , voir exemple de travail en ligne )

0
Mathieu Rodic
def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() else '') + y, 
        name
    ).lower()

Et si nous devons couvrir un cas avec une entrée déjà non camelée:

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

Un exemple horrible utilisant des expressions régulières (vous pourriez facilement nettoyer ceci :)):

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

Fonctionne pour getHTTPResponseCode cependant!

Alternativement, en utilisant 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: Il devrait également être assez facile de voir qu'il est possible d'améliorer des cas comme "Test", car le trait de soulignement est inséré de manière inconditionnelle.

0
Matthew Iselin

Concis sans expressions régulières, mais 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())])
0
Dantalion

Sans aucune bibliothèque:

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 peu lourd, mais

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

Je cherchais une solution au même problème, sauf qu'il me fallait une chaîne; par exemple.

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

En partant des solutions de Nice à deux mots ici, je propose les éléments suivants:

"-".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 logique la plus compliquée consiste à éviter de mettre le premier mot en minuscule. Voici une version plus simple si cela ne vous dérange pas de modifier le premier mot:

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

Bien entendu, vous pouvez pré-compiler les expressions régulières ou les joindre avec un trait de soulignement au lieu d'un trait d'union, comme indiqué dans les autres solutions.

0
Jim Pivarski