Python程式碼閱讀(第21篇):將變數名稱轉換為蛇式命名風格

FelixZ發表於2021-10-22

Python 程式碼閱讀合集介紹:為什麼不推薦Python初學者直接看專案原始碼

本篇閱讀的程式碼實現將變數名稱轉換為蛇式命名風格(snake case)的功能。

本篇閱讀的程式碼片段來自於30-seconds-of-python

snake

from re import sub

def snake(s):
  return '_'.join(
    sub('([A-Z][a-z]+)', r' \1',
    sub('([A-Z]+)', r' \1',
    s.replace('-', ' '))).split()).lower()

# EXAMPLES
snake('camelCase') # 'camel_case'
snake('some text') # 'some_text'
snake('some-mixed_string With spaces_underscores-and-hyphens') # 'some_mixed_string_with_spaces_underscores_and_hyphens'
snake('AllThe-small Things') # "all_the_small_things"

snake函式使用正規表示式將字串變形、分解成單詞,並加上_作為分隔符組合起來。函式主要使用了re模組的substr.replacestr.splitstr.lowerstr.join。在正式分析snake函式的邏輯之前,先介紹下其中使用到的其他函式的作用。

str.replace(old, new[, count])

返回字串的副本,其中出現的所有子字串old都將被替換為new 如果給出了可選引數count,則只替換前count次出現。

str.split(sep=None, maxsplit=-1)

返回一個由字串內單片語成的列表,使用sep作為分隔字串。 如果給出了maxsplit,則最多進行maxsplit次拆分(因此,列表最多會有maxsplit+1個元素)。 如果maxsplit未指定或為-1,則不限制拆分次數(進行所有可能的拆分)。

如果sep未指定或為None,則會應用另一種拆分演算法:連續的空格會被視為單個分隔符,開頭和結尾如果包含空格的話,將不會拆分出空字串。 因此,使用None拆分空字串或僅包含空格的字串將返回 []

>>> '1 2 3'.split()
['1', '2', '3']
>>> '1 2 3'.split(maxsplit=1)
['1', '2 3']
>>> '   1   2   3   '.split()
['1', '2', '3']

str.join(iterable)

返回一個由iterable中的字串拼接而成的字串。

str.lower()

返回原字串的副本,其所有區分大小寫的字元均轉換為小寫。

re.sub(pattern, repl, string, count=0, flags=0)

返回通過使用repl替換在string最左邊非重疊出現的pattern而獲得的字串。 如果樣式沒有找到,則不加改變地返回stringrepl可以是字串或函式。 向後引用像是\6會用樣式中第6組所匹配到的子字串來替換。 例如下面的例子中第一組匹配到的是myfun,所以在替換的時候,\1使用myfun替換,所以在結果中\npy_後面接著的是myfun

帶有'r'字首的字串是原始字串,反斜槓不必做任何特殊處理。 因此r”\n”表示包含'\''n'兩個字元的字串,而"\n"則表示只包含一個換行符的字串。

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

snake執行邏輯

首先分析一下snake函式最裡面的sub函式。先看下輸入引數。

strings.replace('-', ' ')將待轉換的字串中的'-'使用' '替換。

pattern'([A-Z]+)',其中(...)表示他是一個組合,匹配括號內的正規表示式,並在匹配完成之後,組合的內容可以被獲取,並可以在之後用\number轉義序列進行再次匹配或使用,例如上個例子中的\1'([A-Z]+)'的組合表示要匹配一個或多個大寫字母,並儘可能匹配出最長的子字串。

replr' \1',代表使用組合匹配出來的字串前增加一個空格,替換匹配出來的字串。例如'abcDEF'經過匹配和替換將變成'abc DEF'sub('([A-Z]+)', r' \1', 'abcDEF') # 'abc DEF'

因此,snake函式最裡面的sub函式的輸出是將原始字串中的'-'使用' '替換,再匹配字串中的一個或多個連續的大些字母,在前面增加一個空格。例如原始字串是'abc-abcDEF-ABc'經過第一個sub函式轉換後變成'abc abc DEF ABc'(注意'ABc'前面有兩個空格)。

接下來再分析一下第二層的sub函式。還是先看一下輸入引數。

string是上個sub的輸出,在前面的例子中,是'abc abc DEF ABc'(注意'ABc'前面有兩個空格)。

pattern'([A-Z][a-z]+)'。它也是一個組合,表示要匹配一個大寫字母后面跟著一個或多個小寫字母的形式,並儘可能匹配出最長的子字串。

repl還是r' \1',代表使用組合匹配出來的字串前增加一個空格,替換匹配出來的字串。

因此,第二層sub的輸出是簡單的匹配一個大寫字母后面跟著一個或多個小寫字母的形式,在前面加一個空格。繼續使用前面的例子,這層的輸入字串是'abc abc DEF ABc'(注意'ABc'前面有兩個空格),輸出是'abc abc DEF A Bc'(注意'A'前面有兩個空格)。

然後snake函式將第二層sub輸出的字串使用str.split函式分成字串列表。再將得到的字串列表使用'-'作為分隔符組合起來。最後使用str.lower將組合後的字串轉換成小寫。延續上面的例子,最終輸出的字串為:'abc_abc_def_a_bc'

相關文章