Elegancka funkcja Pythona do konwersji CamelCase na snake_case?

333

Przykład:

>>> convert('CamelCase')
'camel_case'
Sridhar Ratnakumar
źródło
28
Aby przekonwertować w innym kierunku, zobacz to inne pytanie dotyczące przepływu stosu.
Nathan
10
nb to NotCamelCasealethisIs
Matt Richards,
5
@MattRichards Jest to kwestia sporna. wiki
NO_NAME
@MattRichards Na przykład w Javie używają obu, CamelCase służy do nazewnictwa definicji klas, podczas gdy camelCase służy do nazewnictwa zainicjowanych zmiennych.
ciemne

Odpowiedzi:

797

Etui na wielbłąda na etui z wężem

import re

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

Jeśli robisz to wiele razy, a powyższe jest wolne, skompiluj wcześniej wyrażenie regularne:

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

Specjalnie do obsługi bardziej zaawansowanych przypadków (nie jest to już odwracalne):

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

Etui z węża na etui z wielbłądem

name = 'snake_case_name'
name = ''.join(word.title() for word in name.split('_'))
print(name)  # SnakeCaseName
epost
źródło
1
To rozwiązanie zawiedzie w następujących przypadkach: _test_Method, __test__Method, _Test, getHTTPresponseCode, __CamelCase i _Camel_Case.
freegnu
6
co powiesz na odwrót? Konwertujesz not_camel_casena notCamelCasei / lub NotCamelCase?
john2x
9
Aby uniknąć podwójnych znaków podkreślenia podczas konwersji np. Camel_Case, dodaj tę linię:s2.replace('__', '_')
Marcus Ahlberg
2
Uwaga: nie jest to bardzo odwracalne. getHTTPResponseCode powinien zostać przekonwertowany na kod get_h_t_t_p_response_code. getHttpResponseCode powinien zostać przekonwertowany na kod get_http_response_code
K2xL
4
@AnmolSinghJaggi Pierwsze wyrażenie regularne obsługuje wielkość liter w akronimie, po której następuje inne słowo (np. „HTTPResponse” -> „HTTP_Response”) LUB bardziej normalny przypadek początkowego małego słowa, po którym następuje wielkie litery (np. „GetResponse” -> ” get_Response ". Drugi regex obsługuje normalny przypadek dwóch nieakronimów (np.„ ResponseCode ”->„ Response_Code ”), a następnie końcowe wywołanie, aby wszystko napisać małymi literami. Zatem„ getHTTPResponseCode ”->„ getHTTP_ResponseCode ”->„ get_HTTP_Response_Code ”- > „get_http_response_code”
Jeff Moser
188

W indeksie pakietów znajduje się biblioteka fleksji, która może obsłużyć te rzeczy za Ciebie. W takim przypadku będziesz szukał inflection.underscore():

>>> inflection.underscore('CamelCase')
'camel_case'
Brad Koch
źródło
44
Nie rozumiem, dlaczego ludzie głosują na użycie niestandardowych funkcji, gdy istnieje świetna biblioteka, która wykonuje to zadanie. Nie powinniśmy wymyślać koła na nowo.
oden
88
@oden Może dlatego, że dodanie całej nowej zależności do wykonywania funkcji jednowierszowej jest delikatnym nadmiernym zabijaniem?
Cecil Curry
11
Na przykład, pewnie to przesada. W przypadku większych aplikacji nie trzeba wymyślać i zaciemniać koła.
Brad Koch,
11
Powraca dużo do „pojedynczej linii”, dlatego jest to więcej niż jedna linia z odpowiednim testowaniem.
studgeek
12
@CecilCurry: Jestem pewien, że jesteś świetnym programistą, ale nie jestem pewien, że nie ma przypadków, których nie wziąłeś pod uwagę - wystarczy spojrzeć na inne odpowiedzi tutaj, aby uzyskać przykłady. Dlatego zawsze wybieram bibliotekę, ponieważ jest to suma doświadczeń znacznie większej liczby deweloperów niż tylko mnie.
Michael Scheper,
104

Nie wiem, dlaczego to wszystko jest takie skomplikowane.

w większości przypadków proste wyrażenie ([A-Z]+)wystarczy

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

Aby zignorować pierwszą postać, wystarczy spojrzeć za siebie (?!^)

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

Jeśli chcesz oddzielić ALLCaps od all_caps i oczekujesz liczb w swoim ciągu, nadal nie musisz wykonywać dwóch osobnych przebiegów, po prostu użyj |tego wyrażenia, ((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))które poradzi sobie z każdym scenariuszem w książce

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

Wszystko zależy od tego, czego chcesz, więc skorzystaj z rozwiązania, które najlepiej odpowiada Twoim potrzebom, ponieważ nie powinno być zbyt skomplikowane.

nJoy!

nickl-
źródło
1
Ostatnia iteracja jest najmądrzejsza, IMO. Trochę zajęło mi zrozumienie, że zastępuje on tylko jeden znak na początku każdego słowa - i to tylko dlatego, że podejście było inne niż to, które sam wymyśliłem. Ładnie wykonane.
Justin Miller
2
Byłem zdziwiony tym, (?!^)że nazywa się to „spojrzenie za siebie”. Chyba, że ​​czegoś mi brakuje, tak naprawdę chcemy tutaj negatywnego spojrzenia, które należy wyrazić jako (?<!^). Z powodów, dla których nie rozumiem, wasze negatywne spojrzenie w przyszłość również (?!^)wydaje się działać ...
Apteryx
7
To nie radzi sobie dobrze z istniejącymi podkreśleniami: "Camel2WARNING_Case_CASE"staje się "camel2_warning_case__case". Możesz dodać (?<!_)negatywny wygląd, aby go rozwiązać: re.sub('((?<=[a-z0-9])[A-Z]|(?!^)(?<!_)[A-Z](?=[a-z]))', r'_\1', "Camel2WARNING_Case_CASE").lower() zwraca 'camel2_warning_case_case'
luckydonald
@Apteryx Masz rację, (?!^)został niepoprawnie nazwany „patrz za siebie” i zamiast tego powinien zostać nazwany negatywnym stwierdzeniem z wyprzedzeniem . Jak pokazuje to miłe wyjaśnienie , negatywne spojrzenia zwykle pojawiają się po szukanym wyrażeniu. Więc można myśleć (?!^)jako „znajdź ''gdzie <start of string>nie wynika”. Rzeczywiście, negatywny lookbehind również działa: można myśleć (?<!^)jako „znaleźć ''gdzie <start of string>nie wyprzedza”.
Nathaniel Jones
17

stringcase jest moją biblioteką do tego celu; na przykład:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'
Amant
źródło
11

Osobiście nie jestem pewien, jak cokolwiek używającego wyrażeń regularnych w pythonie można określić jako eleganckie. Większość odpowiedzi tutaj polega na wykonywaniu sztuczek RE typu „golf golfowy”. Eleganckie kodowanie powinno być łatwe do zrozumienia.

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
źródło
1
+=na sznurkach jest prawie zawsze złym pomysłem. Dołącz do listy i ''.join()na końcu. Lub w tym przypadku po prostu dołącz do niego podkreślenie ...
ThiefMaster
21
W jaki sposób jednowierszowe wyrażenie regularne nie jest z natury lepsze pod każdym względem (w tym czytelności) od nieefektywnej wieloliniowej iteracji znaków i mungingu ciągu siłowego? Python zapewnia obsługę wyrażeń regularnych od razu po wyjęciu z pudełka.
Cecil Curry,
1
@CecilCurry - Wyrażenia regularne są BARDZO złożone. Zobacz kompilator i analizator składni, z którego korzysta Python: svn.python.org/projects/python/trunk/Lib/sre_compile.py & svn.python.org/projects/python/trunk/Lib/sre_parse.py - Proste manipulowanie ciągami, takie jak to prawdopodobnie znacznie szybciej niż RE robi to samo.
Evan Borgstrom
1
+1. Regeksy mogą być prawdziwym ujściem procesora, a przy intensywnych obliczeniach radykalnie obniżą wydajność. W przypadku prostych zadań zawsze preferuj proste funkcje.
Fabien
4
„W przypadku prostych zadań zawsze preferuj proste funkcje” to zdecydowanie dobra rada, ale ta odpowiedź nie jest ani funkcją prostą, ani elegancką. Regex może być wolniejszy, ale
domyślnie złożona
9

Wolę unikać, rejeśli to możliwe:

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'
colidyre
źródło
1
Jest to najbardziej kompaktowy, który pozwala uniknąć korzystania z rebiblioteki i robienia rzeczy tylko w jednym wierszu przy użyciu wbudowanych metod str.methods! Jest podobny do tej odpowiedzi , ale unika używania krojenia i dodatkowego if ... else, po prostu usuwając potencjalnie dodane „_” jako pierwszy znak. Najbardziej to lubię.
colidyre
Za zaakceptowaną odpowiedź, 6.81 µs ± 22.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)ale za tę odpowiedź, 2.51 µs ± 25.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)która jest 2,5 razy szybsza! Kocham to!
WBAR
8
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
Jimmy
źródło
7

Myślę, że to rozwiązanie jest prostsze niż poprzednie odpowiedzi:

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

Które wyjścia:

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

Wyrażenie regularne pasuje do trzech wzorców:

  1. [A-Z]?[a-z]+: Kolejne małe litery, które opcjonalnie zaczynają się od dużej litery.
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): Dwie lub więcej kolejnych wielkich liter. Wykorzystuje lookahead, aby wykluczyć ostatnią wielką literę, jeśli następuje po niej mała litera.
  3. \d+: Kolejne numery.

Korzystając z niego re.findall, otrzymujemy listę pojedynczych „słów”, które można przekonwertować na małe litery i połączyć z podkreślnikami.

obrotowe
źródło
1
Jest tutaj dobry przykład na niezależne tokenizację liczb.
math_law
1
Uszkodzony: konwersja („aB”) -> „a”
adw
5

Nie mam pojęcia, dlaczego używam obu wywołań .sub ()? :) Nie jestem guru wyrażeń regularnych, ale uprościłem funkcję do tej, która jest odpowiednia dla moich niektórych potrzeb, po prostu potrzebowałem rozwiązania do konwersji camelCasedVars z żądania POST na vars_with_underscore:

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

Nie działa z takimi nazwami jak getHTTPResponse, ponieważ słyszałem, że jest to zła konwencja nazewnictwa (powinna być jak getHTTPResponse, to oczywiście, że o wiele łatwiej jest zapamiętać ten formularz).

desper4do
źródło
Zapomniałem wspomnieć, że „{1}” nie jest potrzebne, ale czasem pomaga wyjaśnić mgłę.
desper4do
2
-1: to po prostu nie działa. Spróbuj na przykład z 'HTTPConnectionFactory', tworzy kod 'h_tt_pconnection_factory', kod z zaakceptowanej odpowiedzi tworzy'http_connection_factory'
vartec
4

Oto moje rozwiązanie:

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)

Obsługuje te przypadki narożne omówione w komentarzach. Na przykład, będzie ona przekształcić getHTTPResponseCodesię get_http_response_codetak jak powinien.

Evan Fosmark
źródło
7
-1, ponieważ jest to bardzo skomplikowane w porównaniu do używania wyrażeń regularnych.
Eric O Lebigot,
7
EOL, jestem pewien, że wiele osób, które nie mają wyrażeń regularnych, pomyślałoby inaczej.
Evan Fosmark
To rozwiązanie zawiedzie w następujących przypadkach: _Method, _test_Method , __test__Method, getHTTPrespnseCode, __get_HTTPresponseCode, _Camel_Case, _Test i _test_Method.
freegnu
3
@Evan, ci ludzie byliby złymi programistami.
Jesse Dhillon
3

Dla zabawy:

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

Lub więcej dla zabawy:

>>> 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
źródło
3
c.isupper () zamiast c w ABCEF ... Z
Jimmy
1
Python nie ma wyrażeń regularnych? Szybkie 's / [az] \ K ([AZ] [az]) / _ \ L $ 1 / g; LC $ _”w Perl wykonuje pracę (choć nie obsługuje getHTTPResponseCode dobrze, ale to z oczekiwaniami, które powinny być nazwane getHttpResponseCode)
jrockway
5
str.joinbył przestarzały od wieków . Użyj ''.join(..)zamiast tego.
John Fouhy,
jrockway: Ma wyrażenia regularne za pośrednictwem modułu „re”. Wykonanie tej operacji przy użyciu wyrażenia regularnego nie powinno być trudne, niż opisane tutaj metody.
Matthew Iselin
Python noob tutaj, ale po co zwracać str.join ('', wyjście)? Wystarczy utworzyć kopię?
Tarks
3

Używanie wyrażeń regularnych może być najkrótsze, ale to rozwiązanie jest o wiele bardziej czytelne:

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-
źródło
@blueyed, które jest całkowicie niezwiązane, to pytanie nie ma nic wspólnego z django.
3k-
To tylko przykład, jak HTTPResponseCode, który jest obsługiwany przez stackoverflow.com/a/23561109/15690 .
niebieskawy
3

Tak wiele skomplikowanych metod ... Po prostu znajdź całą grupę „Tytułowa” i dołącz do jej małej wersji z podkreśleniem.

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

Jeśli nie chcesz tworzyć liczb takich jak pierwszy znak grupy lub oddzielnej grupy - możesz użyć ([A-z][a-z0-9]*)maski.

unitto
źródło
2

Nie w standardowej bibliotece, ale znalazłem ten skrypt, który wydaje się zawierać potrzebną funkcjonalność.

Stefano Borini
źródło
2

To nie jest elegancka metoda, jest to implementacja bardzo niskiego poziomu prostej maszyny stanów (maszyna stanu bitfielda), być może najbardziej anty-pythonowy tryb do rozwiązania tego problemu, jednak moduł ponownie implementuje zbyt złożoną maszynę stanu do rozwiązania tej prostej zadanie, więc myślę, że to dobre rozwiązanie.

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

symbol podziału może analizować wszystkie typy przypadków: UpperSEQUENCEInterleaved, under_score, BIG_SYMBOLS i cammelCasedMethods

Mam nadzieję, że się przyda

jdavidls
źródło
1
Ohydne, ale działa około 3 razy szybciej niż metoda regex na moim komputerze. :)
jdiaz5513
1

Lekko dostosowany z https://stackoverflow.com/users/267781/matth, którzy korzystają z generatorów.

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
źródło
1

Spójrz na doskonałą bibliotekę Schematics

https://github.com/schematics/schematics

Pozwala tworzyć struktury danych typowanych, które mogą serializować / deserializować z języka Python do Javascript, np .:

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
źródło
1

Ta prosta metoda powinna wykonać zadanie:

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()
  • Szukamy wielkich liter poprzedzonych dowolną liczbą (lub zero) wielkich liter, a następnie dowolną liczbą małych liter.
  • Podkreślenie jest umieszczane tuż przed wystąpieniem ostatniej dużej litery znalezionej w grupie, a przed tą wielką literą można umieścić ją, jeśli poprzedza ją inna duża litera.
  • Jeśli są podkreślenia końcowe, usuń je.
  • Na koniec cały ciąg wyników jest zamieniany na małe litery.

(wzięte stąd , patrz działający przykład online )

Mathieu Rodic
źródło
To jest odpowiedź na przeciwne pytanie (jak przekonwertować na skrzynkę wielbłąda).
Justin,
1

Wow, właśnie ukradłem to z fragmentów django. ref http://djangosnippets.org/snippets/585/

Całkiem elegancki

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

Przykład:

camelcase_to_underscore('ThisUser')

Zwroty:

'this_user'

REGEX DEMO

Brianray
źródło
1
Zła forma przy użyciu str jako nazwy zmiennej lokalnej.
freegnu
Nie udaje się to źle, jeśli na początku lub na końcu łańcucha znajdują się podkreślenia, a przed wielką literą występują podkreślenia.
freegnu
nie bierze pod uwagę liczb 😬
villy393,
0

Przerażający przykład z użyciem wyrażeń regularnych (można to łatwo wyczyścić :)):

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

Działa jednak dla getHTTPResponseCode!

Alternatywnie, używając 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")

EDYCJA: Powinno być również dość łatwo zauważyć, że jest miejsce na ulepszenia dla przypadków takich jak „Test”, ponieważ podkreślenie jest bezwarunkowo wstawione.

Matthew Iselin
źródło
0

Oto coś, co zrobiłem, aby zmienić nagłówki w pliku rozdzielanym tabulatorami. Pomijam część, w której edytowałem tylko pierwszą linię pliku. Możesz łatwo dostosować go do Pythona za pomocą biblioteki re. Obejmuje to także oddzielanie liczb (ale utrzymuje cyfry razem). Zrobiłem to w dwóch krokach, ponieważ było to łatwiejsze niż mówienie, aby nie umieszczać podkreślenia na początku linii lub tabulacji.

Krok pierwszy ... znajdź wielkie litery lub liczby całkowite poprzedzone małymi literami i poprzedź je znakiem podkreślenia:

Szukaj:

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

Zastąpienie:

\1_\l\2/

Krok drugi ... weź powyższe i uruchom ponownie, aby przekonwertować wszystkie wielkie litery na małe:

Szukaj:

([A-Z])

Zastąpienie (to odwrotny ukośnik, mała litera L, odwrotny ukośnik, jeden):

\l\1
Joe Tricarico
źródło
0

Szukałem rozwiązania tego samego problemu, tyle że potrzebowałem łańcucha; na przykład

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

Zaczynając od fajnych rozwiązań dwóch słów tutaj, wymyśliłem:

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

Większość skomplikowanej logiki polega na unikaniu zmniejszania pierwszego słowa. Oto prostsza wersja, jeśli nie masz nic przeciwko zmianie pierwszego słowa:

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

Oczywiście można wstępnie kompilować wyrażenia regularne lub łączyć z podkreśleniem zamiast łącznika, jak omówiono w innych rozwiązaniach.

Jim Pivarski
źródło
0

Zwięzłe bez wyrażeń regularnych, ale 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
źródło
0

Bez biblioteki:

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

Trochę ciężko, ale

CamelCamelCamelCase ->  camel_camel_camel_case
HTTPRequest         ->  http_request
GetHTTPRequest      ->  get_http_request
getHTTPRequest      ->  get_http_request
bibmartin
źródło
0

Bardzo ładne RegEx zaproponowane na tej stronie :

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

Jeśli python ma metodę podziału ciągu, powinien działać ...

W Javie:

String s = "loremIpsum";
words = s.split("(?&#60;!^)(?=[A-Z])");
Jmini
źródło
Niestety moduł wyrażeń regularnych Python (od wersji 3.6) nie obsługuje podziału na dopasowania o zerowej długości.
rspeed
0
def convert(name):
    return reduce(
        lambda x, y: x + ('_' if y.isupper() else '') + y, 
        name
    ).lower()

A jeśli musimy pokryć skrzynkę z już nie rozpiętym wejściem:

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

Na wypadek, gdyby ktoś musiał przekształcić pełny plik źródłowy, oto skrypt, który to zrobi.

# 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.
źródło
-1

Miałem dość szczęścia z tym:

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)

Może to oczywiście być zoptymalizowane pod kątem szybkości jest malutki kawałek, jeśli chcesz.

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
źródło
-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
źródło
-3

Posługiwać się: str.capitalize() do konwersji pierwszej litery ciągu (zawartej w zmiennej str) na wielką literę i zwraca cały ciąg.

Przykład: Polecenie: „hello” .capitalize () Dane wyjściowe: Hello

Arshin
źródło
Nie ma to związku z pytaniem - OP chce CamelCase -> snake_case, a nie wielkie litery.
Brad Koch,