ฟังค์ชั่น Elegant Python เพื่อแปลง CamelCase เป็น snake_case?


333

ตัวอย่าง:

>>> convert('CamelCase')
'camel_case'

28
หากต้องการแปลงในทิศทางอื่นให้ดูคำถามสแต็คโอเวอร์โฟลว์อื่น
นาธาน

10
nb ว่าNotCamelCaseแต่thisIs
แมตต์ริชาร์ด

5
@MattRichards มันเป็นเรื่องของความขัดแย้ง wiki
NO_NAME

@MattRichards ตัวอย่างเช่นใน Java ที่ใช้ทั้งคู่ CamelCase ใช้สำหรับการตั้งชื่อนิยาม Class ในขณะที่ camelCase ใช้สำหรับการตั้งชื่อตัวแปรที่กำหนดค่าเริ่มต้น
มืด

คำตอบ:


797

กรณีอูฐเพื่องูกรณี

import re

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

หากคุณทำหลายครั้งและข้างต้นช้ารวบรวม regex ล่วงหน้า:

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

หากต้องการจัดการกรณีขั้นสูงมากเป็นพิเศษ (นี่จะไม่สามารถย้อนกลับได้อีกต่อไป):

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

กรณีงูกรณีอูฐ

name = 'snake_case_name'
name = ''.join(word.title() for word in name.split('_'))
print(name)  # SnakeCaseName

1
การแก้ปัญหานี้ล้มเหลวในกรณีเหล่านี้: _test_Method, __test__Method, _Test, getHTTPresponseCode, __CamelCase และ _Camel_Case
freegnu

6
ย้อนกลับล่ะ แปลงnot_camel_caseไปnotCamelCaseและ / หรือNotCamelCase?
john2x

9
เพื่อหลีกเลี่ยงการขีดเส้นใต้คู่เมื่อแปลงเช่น camel_Case ให้เพิ่มบรรทัดนี้:s2.replace('__', '_')
Marcus Ahlberg

2
หมายเหตุนี่ไม่สามารถย้อนกลับได้มาก getHTTPResponseCode ควรแปลงเป็น get_h_t_t_p_response_code getHttpResponseCode ควรแปลงเป็น get_http_response_code
K2xL

4
@AnmolSinghJaggi regex ตัวแรกจัดการกับตัวพิมพ์เล็กของตัวย่อตามด้วยคำอื่น (เช่น "HTTPResponse" -> "HTTP_Response") หรือกรณีปกติมากขึ้นของคำตัวพิมพ์เล็กเริ่มต้นตามด้วยคำตัวพิมพ์ใหญ่ (เช่น "getResponse" -> " get_Response ". regex ตัวที่สองจัดการกับตัวพิมพ์ใหญ่ที่ไม่ใช่ตัวย่อสองตัว (เช่น" ResponseCode "->" Response_Code ") ตามด้วยการเรียกขั้นสุดท้ายเพื่อพิมพ์เล็กทุกอย่างดังนั้น" getHTTPResponseCode "->" getHTTP_ResponseCode "->" getHTTP_ResponseCode " > "get_http_response_code"
Jeff Moser

188

มีห้องสมุดผันในดัชนีแพคเกจที่สามารถจัดการสิ่งเหล่านี้ให้คุณได้ ในกรณีนี้คุณจะมองหาinflection.underscore():

>>> inflection.underscore('CamelCase')
'camel_case'

44
ฉันไม่เข้าใจว่าทำไมคนถึงลงคะแนนให้ใช้ฟังก์ชั่นที่กำหนดเองเมื่อมีห้องสมุดที่ยอดเยี่ยมที่ทำงานนี้ เราไม่ควรคิดค้นล้อใหม่
oden

88
@ oden อาจเป็นเพราะการเพิ่มการพึ่งพาใหม่ทั้งหมดในการทำงานของฟังก์ชั่นบรรทัดเดียวจะเปราะบางเกินฆ่า?
เซซิลแกงกะหรี่

11
ตัวอย่างหนึ่งให้แน่ใจว่ามันเกินความจริง ข้ามแอพพลิเคชั่นที่ใหญ่ขึ้นไม่จำเป็นต้องสร้างใหม่และทำให้งงงวยล้อ
แบรดโคช์

11
Regexes กลับมากเป็น "บรรทัดเดียว" ซึ่งเป็นเหตุผลที่มันมากกว่าหนึ่งบรรทัดกับการทดสอบที่เหมาะสม
studgeek

12
@CecilCurry: ฉันแน่ใจว่าคุณเป็นโปรแกรมเมอร์ที่ยอดเยี่ยม แต่ฉันไม่แน่ใจว่าไม่มีกรณีที่คุณไม่ได้พิจารณา - เพียงแค่ดูคำตอบอื่น ๆ ที่นี่เพื่อดูตัวอย่าง นั่นเป็นเหตุผลที่ฉันจะเลือกห้องสมุดเสมอเพราะมันเป็นประสบการณ์แบบรวมของผู้พัฒนามากกว่าฉัน
Michael Scheper

104

ฉันไม่รู้ว่าทำไมสิ่งเหล่านี้จึงซับซ้อน

สำหรับกรณีส่วนใหญ่การแสดงออกอย่างง่าย ๆ([A-Z]+)จะใช้กลอุบาย

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

ในการเพิกเฉยต่ออักขระตัวแรกเพียงเพิ่มการมองหลัง (?!^)

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

หากคุณต้องการแยก ALLCaps ไปยัง all_caps และคาดหวังว่าตัวเลขในสตริงของคุณคุณยังไม่จำเป็นต้องทำการแยกกันสองครั้งเพียงแค่ใช้|การแสดงออกนี้((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))สามารถจัดการกับทุกสถานการณ์ในหนังสือ

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

ทุกอย่างขึ้นอยู่กับสิ่งที่คุณต้องการดังนั้นใช้โซลูชันที่เหมาะสมกับความต้องการของคุณมากที่สุดเนื่องจากไม่ควรซับซ้อนเกินไป

Njoy!


1
การทำซ้ำครั้งล่าสุดคือ IMO ที่ฉลาดที่สุด ฉันใช้เวลาเล็กน้อยที่จะเข้าใจว่ามันเป็นเพียงการแทนที่ตัวละครเดียวที่จุดเริ่มต้นของแต่ละคำ - และนั่นก็เป็นเพียงเพราะวิธีการที่แตกต่างกว่าหนึ่งฉันจะมากับตัวเอง ทำได้ดีมาก
Justin Miller

2
ฉันงงงวยกับ(?!^)การแสดงออกที่เรียกว่าการมองไปข้างหลัง (?<!^)เว้นแต่ฉันหายไปบางสิ่งบางอย่างในสิ่งที่เราต้องการจริงๆที่นี่เป็นเชิงลบรูปลักษณ์ที่อยู่เบื้องหลังซึ่งควรจะแสดงเป็น ด้วยเหตุผลที่ฉันไม่สามารถเข้าใจการมองไปในแง่ลบของคุณได้(?!^)ดูเหมือนว่าจะทำงานเช่นกัน ...
Apteryx

7
นี้ไม่ได้จัดการกับนิยายขีดกัน: กลายเป็น"Camel2WARNING_Case_CASE" "camel2_warning_case__case"คุณสามารถเพิ่มความคิด(?<!_)เชิงลบเพื่อแก้ไข: re.sub('((?<=[a-z0-9])[A-Z]|(?!^)(?<!_)[A-Z](?=[a-z]))', r'_\1', "Camel2WARNING_Case_CASE").lower() คืน 'camel2_warning_case_case'
luckydonald

@Apteryx คุณกำลังขวา, (?!^)ถูกไม่ถูกต้องเรียกว่า "ดูเบื้องหลัง" และควรได้รับการแทนเรียกว่ายืนยัน lookahead เชิงลบ เมื่อคำอธิบายที่ดีนี้แสดงให้เห็นว่าผู้มองเชิงลบมักตามหลังนิพจน์ที่คุณค้นหา ดังนั้นคุณสามารถคิดว่า(?!^)เป็น "ค้นหา''ที่<start of string>ไม่ปฏิบัติตาม" แท้จริงเป็นลบ lookbehind ยังทำงาน: คุณคิด(?<!^)ว่า "หา''ที่<start of string>จะล่วงหน้าไปก่อน"
นาธาเนียลโจนส์


11

โดยส่วนตัวแล้วฉันไม่แน่ใจว่าสิ่งใดที่ใช้นิพจน์ปกติในไพ ธ อนสามารถอธิบายได้ว่าสวยงาม คำตอบส่วนใหญ่ที่นี่เป็นเพียงการทำ "รหัสกอล์ฟ" พิมพ์ RE เทคนิค การเข้ารหัสที่หรูหราควรจะเข้าใจได้ง่าย

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'

1
+=ในสตริงมักจะเป็นความคิดที่ดี ต่อท้ายรายการและ''.join()ท้ายที่สุด หรือในกรณีนี้เพียงแค่เข้าร่วมได้ด้วยการขีดเส้นใต้ ...
ThiefMaster

21
การแสดงออกปกติแบบบรรทัดเดียวนั้นไม่ดีกว่าในทุก ๆ ทางที่ใช้งานได้จริง (รวมถึงความสามารถในการอ่านได้) ในการทำซ้ำตัวอักษรหลายบรรทัดที่ไม่มีประสิทธิภาพและการบังคับสตริงที่ดุร้าย? Python ให้การสนับสนุนการแสดงออกปกตินอกกรอบด้วยเหตุผล
เซซิลแกง

1
@CecilCurry - การแสดงออกปกติมีความซับซ้อนมาก ดูคอมไพเลอร์และ parser ที่ Python ใช้: svn.python.org/projects/python/trunk/Lib/sre_compile.py & svn.python.org/projects/python/trunk/Lib/sre_parse.py - การจัดการสตริงอย่างง่ายเช่น นี่น่าจะเร็วกว่า RE ที่ทำเช่นเดียวกัน
Evan Borgstrom

1
+1 Regexes สามารถเป็นซีพียูตัวจริงและการคำนวณที่เข้มข้นจะลดประสิทธิภาพของคุณลงอย่างมาก สำหรับงานง่าย ๆ ชอบฟังก์ชั่นง่าย ๆ เสมอ
Fabien

4
"สำหรับงานที่เรียบง่ายมักจะชอบฟังก์ชั่นที่เรียบง่าย" เป็นคำแนะนำที่ดี แต่คำตอบนี้ไม่ใช่ฟังก์ชั่นที่ง่ายและสง่างาม Regex อาจช้าลง แต่การเริ่มต้นฟังก์ชันที่ซับซ้อนเช่นนี้ (นั่นคือยังไม่ผ่านการทดสอบและมีข้อผิดพลาดที่อาจเกิดขึ้นได้หลายอย่าง) นั้นเป็นการเพิ่มประสิทธิภาพก่อนเวลาอันสมบูรณ์
kevlarr

9

ฉันชอบที่จะหลีกเลี่ยงreถ้าเป็นไปได้:

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'

1
นี่คือคอมแพคที่สุดที่หลีกเลี่ยงการใช้reไลบรารีและทำสิ่งต่าง ๆ ในบรรทัดเดียวโดยใช้ str.methods ในตัวเท่านั้น! มันคล้ายกับคำตอบนี้แต่หลีกเลี่ยงการใช้การแบ่งส่วนและเพิ่มเติมif ... elseโดยเพียงแค่การเพิ่ม "_" เป็นอักขระตัวแรก ฉันชอบสิ่งนี้มากที่สุด
colidyre

สำหรับคำตอบที่ยอมรับ6.81 µs ± 22.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)แต่สำหรับคำตอบ2.51 µs ± 25.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)นี้เร็วกว่า 2.5 เท่า! รักสิ่งนี้!
WBAR


7

ฉันคิดว่าวิธีนี้ตรงไปตรงมามากกว่าคำตอบก่อนหน้า:

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

ผลลัพธ์ใด:

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

นิพจน์ทั่วไปตรงกับสามรูปแบบ:

  1. [A-Z]?[a-z]+: ตัวอักษรตัวพิมพ์เล็กที่ต่อเนื่องกันซึ่งเป็นทางเลือกเริ่มต้นด้วยตัวอักษรตัวพิมพ์ใหญ่
  2. [A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$): อักษรตัวพิมพ์ใหญ่สองตัวหรือมากกว่าติดต่อกัน มันใช้ lookahead เพื่อแยกอักษรตัวพิมพ์ใหญ่ตัวสุดท้ายถ้าตามด้วยตัวอักษรตัวพิมพ์เล็ก
  3. \d+: ตัวเลขที่ต่อเนื่องกัน

โดยการใช้งานre.findallเราจะได้รับรายชื่อ "คำศัพท์" แต่ละคำที่สามารถแปลงเป็นตัวพิมพ์เล็กและเชื่อมโยงกับขีดล่าง


1
มีตัวอย่างที่ดีที่นี่เพื่อรับโทเค็นตัวเลขได้อย่างอิสระ
math_law

1
Broken: แปลง ( "aB") -> 'a'
ADW

5

ฉันไม่ทราบว่าทำไมใช้ทั้ง. sub () โทร? :) ฉันไม่ใช่กูรู regex แต่ฉันทำให้ฟังก์ชั่นนี้ง่ายขึ้นซึ่งเหมาะกับความต้องการของฉันฉันแค่ต้องการโซลูชันในการแปลง camelCasedVars จากคำขอ POST เป็น vars_with_underscore:

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

มันใช้งานไม่ได้กับชื่อเช่น getHTTPResponse เพราะฉันได้ยินมาว่ามันเป็นวิธีการตั้งชื่อที่ไม่ดี (น่าจะเหมือน getHttpResponse เห็นได้ชัดว่ามันจดจำรูปแบบนี้ได้ง่ายกว่า)


ฉันลืมที่จะพูดถึงว่าไม่จำเป็นต้องใช้ '{1}' แต่บางครั้งก็ช่วยอธิบายหมอกบางอย่างได้
desper4do

2
-1: สิ่งนี้ไม่ทำงาน ลองด้วยตัวอย่างเช่นกับการ'HTTPConnectionFactory'สร้างรหัสของคุณ'h_tt_pconnection_factory'รหัสจากคำตอบที่ยอมรับก่อให้เกิด'http_connection_factory'
vartec

4

นี่คือทางออกของฉัน:

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)

สนับสนุนกรณีมุมเหล่านั้นกล่าวถึงในความคิดเห็น ตัวอย่างเช่นมันจะแปลงgetHTTPResponseCodeเป็นget_http_response_codeอย่างที่ควรจะเป็น


7
-1 เนื่องจากมีความซับซ้อนมากเมื่อเทียบกับการใช้ regexps
Eric O Lebigot

7
EOL ฉันแน่ใจว่าคนที่ไม่ได้จดบันทึกจำนวนมากจะคิดเป็นอย่างอื่น
Evan Fosmark

การแก้ปัญหานี้ล้มเหลวในกรณีเหล่านี้: _Method, _test_Method , __test__Method, getHTTPrespnseCode, __get_HTTPresponseCode, _Camel_Case, _Test และ _test_Method
freegnu

3
@Evan คนเหล่านั้นคงจะเป็นโปรแกรมเมอร์ที่ไม่ดี
Jesse Dhillon

3

เพื่อความสนุกของมัน

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

หรือมากกว่าเพื่อความสนุกของมัน:

>>> 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
c.isupper () มากกว่า c ใน ABCEF ... Z
Jimmy

1
Python ไม่มี regexes ใช่ไหม a / s [az] \ K ([AZ] [az]) / a LC $ _' ใน Perl ไม่ทำงาน (แม้ว่ามันจะไม่ได้จัดการ getHTTPResponseCode ดี แต่ที่คาดว่าควรตั้งชื่อ getHttpResponseCode)
jrockway

5
str.joinได้เลิกสำหรับทุกเพศทุกวัย ใช้''.join(..)แทน
John Fouhy

jrockway: มันมีการแสดงออกปกติผ่านโมดูล "ใหม่" ไม่ควรยากเกินไปที่จะทำให้งานนี้ใช้ regex มากกว่าวิธีการโพสต์ที่นี่
แมทธิวอีเซยิน

Python noob ที่นี่ แต่ทำไม return str.join ('', output)? เพียงเพื่อสร้างสำเนา?
Tarks

3

การใช้ regexes อาจสั้นที่สุด แต่วิธีนี้สามารถอ่านได้ง่ายขึ้น:

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

@ blueyed ที่ไม่เกี่ยวข้องอย่างสมบูรณ์คำถามนี้ไม่มีอะไรเกี่ยวข้องกับ django
3k-

มันเป็นเพียงแค่ตัวอย่างเช่น HTTPResponseCode ซึ่งจะถูกจัดการโดยstackoverflow.com/a/23561109/15690
blueyed

3

วิธีการที่ซับซ้อนมากมาย ... เพียงแค่ค้นหากลุ่ม "Titled" ทั้งหมดและเข้าร่วมตัวแปรที่ต่ำกว่าพร้อมขีดล่าง

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

หากคุณไม่ต้องการสร้างตัวเลขเช่นตัวละครตัวแรกของกลุ่มหรือกลุ่มแยกคุณสามารถใช้([A-z][a-z0-9]*)หน้ากาก



2

นี่ไม่ใช่วิธีที่สง่างามคือการใช้งาน 'สถานะต่ำ' อย่างง่ายของเครื่องสถานะเรียบง่าย (เครื่องสถานะบิตฟิลด์) อาจเป็นโหมดต่อต้าน pythonic ส่วนใหญ่ในการแก้ไขปัญหานี้อย่างไรก็ตามโมดูลใหม่ยังใช้เครื่องรัฐที่ซับซ้อนเกินไป งานดังนั้นฉันคิดว่านี่เป็นทางออกที่ดี

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 สามารถแยกวิเคราะห์เคสทุกชนิด: UpperSEQUENCE อินเทอร์เนล, under_score, BIG_SYMBOLS และ cammelCasedMethods

ฉันหวังว่ามันจะมีประโยชน์


1
มันน่ากลัว แต่มันทำงานได้เร็วกว่าวิธี regex ประมาณ 3 เท่าในเครื่องของฉัน :)
jdiaz5513


1

ดู lib Schematics ที่ยอดเยี่ยม

https://github.com/schematics/schematics

จะช่วยให้คุณสร้างโครงสร้างข้อมูลที่พิมพ์ที่สามารถทำให้เป็นอนุกรม / deserialize จากงูหลามจาวาสคริปต์รสชาติเช่น:

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

วิธีการง่ายๆนี้ควรทำงาน:

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()
  • เรามองหาตัวอักษรพิมพ์ใหญ่ที่นำหน้าด้วยตัวพิมพ์ใหญ่ (หรือศูนย์) จำนวนใด ๆ และตามด้วยอักขระตัวพิมพ์เล็กจำนวนเท่าใดก็ได้
  • ขีดล่างถูกวางไว้ก่อนการเกิดขึ้นของตัวอักษรตัวใหญ่สุดท้ายที่พบในกลุ่มและสามารถวางไว้ก่อนตัวอักษรตัวใหญ่ในกรณีที่มันจะนำหน้าด้วยตัวพิมพ์ใหญ่อื่น ๆ
  • หากมีการขีดล่างขีดล่างเอาออก
  • ในที่สุดสตริงผลลัพธ์ทั้งหมดจะถูกเปลี่ยนเป็นตัวพิมพ์เล็ก

(นำมาจากที่นี่ดูตัวอย่างการทำงานออนไลน์ )


นี่คือคำตอบสำหรับคำถามตรงข้าม (วิธีแปลงเป็นตัวอูฐ)
Justin

1

ว้าวฉันเพิ่งขโมยมาจากตัวอย่าง django อ้างอิงhttp://djangosnippets.org/snippets/585/

สวยสง่า

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

ตัวอย่าง:

camelcase_to_underscore('ThisUser')

ผลตอบแทน:

'this_user'

สาธิต REGEX


1
รูปแบบไม่ดีที่ใช้ str เป็นชื่อตัวแปรท้องถิ่น
freegnu

สิ่งนี้จะล้มเหลวอย่างน่าสมเพชหากมีขีดล่างใด ๆ ที่จุดเริ่มต้นหรือจุดสิ้นสุดของสตริงและหากมีขีดล่างใด ๆ ก่อนตัวพิมพ์ใหญ่
freegnu

ไม่คำนึงถึงหมายเลขบัญชี😬
villy393

0

ตัวอย่างที่น่ากลัวโดยใช้นิพจน์ทั่วไป (คุณสามารถล้างสิ่งนี้ได้อย่างง่ายดาย :)):

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

ใช้งานได้กับ getHTTPResponseCode แม้ว่า!

อีกทางเลือกหนึ่งโดยใช้แลมบ์ดา:

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

แก้ไข: มันควรจะค่อนข้างง่ายที่จะเห็นว่ามีห้องพักสำหรับการปรับปรุงสำหรับกรณีเช่น "ทดสอบ" เพราะแทรกขีดเส้นใต้โดยไม่มีเงื่อนไข


0

นี่คือสิ่งที่ฉันทำเพื่อเปลี่ยนส่วนหัวของไฟล์ที่คั่นด้วยแท็บ ฉันจะข้ามส่วนที่ฉันแก้ไขบรรทัดแรกของไฟล์เท่านั้น คุณสามารถปรับให้เข้ากับ Python ได้อย่างง่ายดายด้วยไลบรารีใหม่ รวมถึงการแยกตัวเลขออกด้วย ฉันทำในสองขั้นตอนเพราะง่ายกว่าบอกว่าอย่าใส่ขีดเส้นใต้ที่จุดเริ่มต้นของบรรทัดหรือแท็บ

ขั้นตอนที่หนึ่ง ... ค้นหาตัวอักษรตัวพิมพ์ใหญ่หรือจำนวนเต็มนำหน้าด้วยตัวอักษรตัวเล็กและนำหน้าด้วยเครื่องหมายขีดล่าง:

ค้นหา:

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

เปลี่ยน:

\1_\l\2/

ขั้นตอนที่สอง ... ใช้ด้านบนและเรียกใช้อีกครั้งเพื่อแปลงตัวพิมพ์ใหญ่ทั้งหมดเป็นตัวพิมพ์เล็ก:

ค้นหา:

([A-Z])

การแทนที่ (นั่นคือแบ็กสแลช, ตัวพิมพ์เล็ก L, แบ็กสแลช, หนึ่งอัน):

\l\1

0

ฉันกำลังมองหาวิธีแก้ไขปัญหาเดียวกันยกเว้นว่าฉันต้องการโซ่ เช่น

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

เริ่มต้นจากการแก้ปัญหาสองคำที่ดีที่นี่ฉันมาด้วยต่อไปนี้:

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

ตรรกะที่ซับซ้อนส่วนใหญ่คือการหลีกเลี่ยงการลดคำแรก นี่เป็นเวอร์ชั่นที่ง่ายกว่านี้ถ้าคุณไม่เปลี่ยนคำแรก:

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

แน่นอนคุณสามารถรวบรวมการแสดงออกปกติหรือเข้าร่วมกับขีดล่างแทนยัติภังค์ตามที่กล่าวไว้ในการแก้ปัญหาอื่น ๆ


0

กระชับโดยไม่มีการแสดงออกปกติ แต่ 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

ไม่มีห้องสมุด:

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

ค่อนข้างหนัก แต่

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

0

RegEx ที่ดีมากเสนอบนเว็บไซต์นี้ :

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

ถ้าหลามมีวิธีการแบ่งสตริงมันควรจะทำงาน ...

ใน Java:

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

น่าเสียดายที่โมดูลการแสดงผลปกติของ Python ไม่สนับสนุนการแยกการแข่งขันที่มีความยาวเป็นศูนย์
rspeed

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

และถ้าเราต้องการที่จะครอบคลุมกรณีที่มีการป้อนข้อมูลอูฐแล้วยกเลิกการอูฐ:

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

0

ในกรณีที่มีคนต้องการแปลงไฟล์ต้นฉบับแบบสมบูรณ์นี่คือสคริปต์ที่จะทำ

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

-1

ฉันโชคดีมากกับสิ่งนี้:

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)

นี้เห็นได้ชัดอาจจะเหมาะสำหรับความเร็วเล็กบิตถ้าคุณต้องการ

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)

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

-3

ใช้: str.capitalize()เพื่อแปลงอักษรตัวแรกของสตริง (มีอยู่ในตัวแปร str) เป็นตัวพิมพ์ใหญ่และส่งคืนสตริงทั้งหมด

ตัวอย่าง: คำสั่ง: "hello" .capitalize () เอาท์พุท: สวัสดี


สิ่งนี้ไม่เกี่ยวข้องกับคำถาม - OP ต้องการ CamelCase -> snake_case ไม่ใช่ตัวพิมพ์ใหญ่
แบรดโคช์ส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.