簡體   English   中英

優雅的Python函數將CamelCase轉換為snake_case?

[英]Elegant Python function to convert CamelCase to snake_case?

例子:

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

駱駝套到蛇套

import re

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

如果你多次這樣做並且上面的很慢,請事先編譯正則表達式:

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

添加帶有兩個或更多下划線的案例:

def to_snake_case(name):
    name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    name = re.sub('__([A-Z])', r'_\1', name)
    name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', name)
    return name.lower()

蛇案到駱駝案

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

包索引中有一個屈折庫可以為您處理這些事情。 在這種情況下,您將尋找inflection.underscore()

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

我不知道為什么這些都這么復雜。

在大多數情況下,簡單的表達式([AZ]+)就可以解決問題

>>> 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])[AZ]|(?!^)[AZ](?=[az]))可以處理本書中的幾乎所有場景

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

這完全取決於您想要什么,因此請使用最適合您需求的解決方案,因為它不應過於復雜。

快樂!

避免庫和正則表達式:

def camel_to_snake(s):
    return ''.join(['_'+c.lower() if c.isupper() else c for c in s]).lstrip('_')
>>> camel_to_snake('ThisIsMyString')
'this_is_my_string'

stringcase是我的首選庫; 例如:

>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'

就我個人而言,我不確定在 python 中使用正則表達式的任何東西都可以被描述為優雅。 這里的大多數答案只是在做“代碼高爾夫”類型的 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'

我認為這個解決方案比以前的答案更直接:

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. [AZ]?[az]+ :連續的小寫字母,可選擇以大寫字母開頭。
  2. [AZ]{2,}(?=[AZ][az]|\\d|\\W|$) :兩個或多個連續的大寫字母。 如果最后一個大寫字母后跟一個小寫字母,它會使用前瞻來排除最后一個大寫字母。
  3. \\d+ :連續數字。

通過使用re.findall我們可以得到一個單獨的“單詞”列表,這些單詞可以轉換為小寫並用下划線連接。

''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()

我不明白為什么同時使用 .sub() 調用? :) 我不是正則表達式大師,但我將函數簡化為這個函數,它適合我的某些需求,我只需要一個解決方案,將 POST 請求中的 camelCasedVars 轉換為 vars_with_underscore:

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

它不適用於像 getHTTPResponse 這樣的名稱,因為我聽說它是​​糟糕的命名約定(應該像 getHttpResponse,顯然,記住這種形式要容易得多)。

這是我的解決方案:

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

為了它的樂趣:

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

使用正則表達式可能是最短的,但這個解決方案更具可讀性:

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

這不是一個優雅的方法,是一個簡單狀態機(位域狀態機)的非常“低級”的實現,可能是解決這個問題的最反 Pythonic 模式,但是 re 模塊也實現了一個過於復雜的狀態機來解決這個簡單的問題任務,所以我認為這是一個很好的解決方案。

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 可以解析所有 case 類型:UpperSEQUENCEInterleaved、under_score、BIG_SYMBOLS 和 cammelCasedMethods

我希望它有用

這么多復雜的方法......只需找到所有“標題”組並用下划線加入其小寫變體。

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

如果你不想讓數字像組的第一個字符或單獨的組一樣 - 你可以使用([Az][a-z0-9]*)掩碼。

這個簡單的方法應該可以完成這項工作:

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()
  • 我們尋找前面有任意數量(或零)大寫字母,后面跟着任意數量小寫字符的大寫字母。
  • 下划線放在組中最后一個大寫字母出現之前,如果前面有其他大寫字母,則可以放在該大寫字母之前。
  • 如果有尾隨下划線,請刪除它們。
  • 最后,將整個結果字符串更改為小寫。

(取自此處,請參閱在線工作示例

輕松改編自使用生成器的https://stackoverflow.com/users/267781/matth

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

看看優秀的 Schematics 庫

https://github.com/schematics/schematics

它允許您創建可以從 python 序列化/反序列化到 Javascript 風格的類型化數據結構,例如:

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

不在標准庫中,但我發現這個模塊似乎包含您需要的功能。

使用正則表達式的可怕示例(您可以輕松清理它:)):

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!

或者,使用 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")

編輯:也應該很容易看出“測試”之類的情況還有改進的余地,因為下划線是無條件插入的。

我正在尋找相同問題的解決方案,只是我需要一個鏈條; 例如

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

當然,您可以預編譯正則表達式或使用下划線代替連字符連接,如其他解決方案中所述。

簡潔,沒有正則表達式,但是 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())])

沒有任何圖書館:

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

以防萬一有人需要轉換完整的源文件,這里有一個腳本可以做到這一點。

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

哇,我只是從 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'

正則表達式演示

本網站上提出的非常好的正則表達式:

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

如果python有一個String Split方法,它應該可以工作......

在 Java 中:

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

這是我為更改制表符分隔文件的標題所做的一些事情。 我省略了我只編輯文件第一行的部分。 您可以使用 re 庫輕松地將其調整為 Python。 這還包括分離數字(但將數字保持在一起)。 我分兩步完成,因為這比告訴它不要在行或制表符的開頭放置下划線更容易。

第一步...找到前面有小寫字母的大寫字母或整數,並在它們前面加上下划線:

搜索:

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

替代品:

\1_\l\2/

第二步......采用上述並再次運行以將所有大寫字母轉換為小寫字母:

搜索:

([A-Z])

替換(即反斜杠,小寫 L,反斜杠,一):

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

使用: str.capitalize()將字符串的第一個字母(包含在變量 str 中)轉換為大寫字母並返回整個字符串。

示例:命令:“hello”.capitalize() 輸出:Hello

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM