【发布时间】:2010-11-13 14:19:29
【问题描述】:
例子:
>>> convert('CamelCase')
'camel_case'
【问题讨论】:
标签: python camelcasing
例子:
>>> convert('CamelCase')
'camel_case'
【问题讨论】:
标签: python camelcasing
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
【讨论】:
not_camel_case 转换为notCamelCase 和/或NotCamelCase?
s2.replace('__', '_')
包索引中有一个inflection library 可以为您处理这些事情。在这种情况下,您会寻找inflection.underscore():
>>> inflection.underscore('CamelCase')
'camel_case'
【讨论】:
我不知道为什么这些都这么复杂。
在大多数情况下,简单的表达式 ([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'
这完全取决于您想要什么,因此请使用最适合您需求的解决方案,因为它不应过于复杂。
开心!
【讨论】:
(?!^) 表达式被称为后视感到困惑。除非我遗漏了什么,否则我们在这里真正想要的是消极的后视,应该表示为(?<!^)。由于某些原因,我无法理解您的负面预测 (?!^) 似乎也有效......
"Camel2WARNING_Case_CASE" 变为 "camel2_warning_case__case"。你可以添加一个(?<!_) 否定的lookbehind 来解决它:re.sub('((?<=[a-z0-9])[A-Z]|(?!^)(?<!_)[A-Z](?=[a-z]))', r'_\1', "Camel2WARNING_Case_CASE").lower() 返回'camel2_warning_case_case'
(?!^) 被错误地称为“后视”,而应该被称为否定前瞻断言。正如this nice explanation 所示,否定前瞻通常出现在您要搜索的表达式之后。因此,您可以将(?!^) 视为“查找'',其中<start of string> 不跟随”。事实上,消极的后视也有效:您可以将(?<!^) 视为“找到'',其中<start of string> 不在之前”。
避免使用库和正则表达式:
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'
【讨论】:
re 库并且只使用内置的str.methods 只在一行中做这些事情!它类似于this answer,但通过简单地剥离可能添加的“_”作为第一个字符来避免使用切片和附加if ... else。我最喜欢这个。
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 倍!喜欢这个!
HIThereHOWIsItGoing, how_are_YoU_TeST。虽然我同意它确实可以很好地完成普通的骆驼案到蛇案
stringcase 是我的首选库;例如:
>>> from stringcase import pascalcase, snakecase
>>> snakecase('FooBarBaz')
'foo_bar_baz'
>>> pascalcase('foo_bar_baz')
'FooBarBaz'
【讨论】:
hello world,它会将_ 添加为hello__world 这不好
stringcase.snakecase('hello world') 返回'hello_world'(一个下划线)
hello world,它将添加双_
我认为这个解决方案比以前的答案更直接:
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
正则表达式匹配三种模式:
[A-Z]?[a-z]+:可选以大写字母开头的连续小写字母。[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$):两个或多个连续的大写字母。如果最后一个大写字母后跟一个小写字母,它会使用先行排除它。\d+:连续的数字。通过使用re.findall,我们得到一个单独的“单词”列表,这些单词可以转换为小写并用下划线连接。
【讨论】:
r"[A-Z]?[a-z]+|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)|\d+|[A-Z]{2,}|[A-Z]$"
就我个人而言,我不确定如何将在 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'
【讨论】:
+= 在字符串上几乎总是一个坏主意。附加到一个列表并在最后''.join() 它。或者在这种情况下,只需用下划线连接它...
''.join('_'+c.lower() if c.isupper() else c for c in "DeathToCamelCase").strip('_')
re.sub("(.)([A-Z])", r'\1_\2', 'DeathToCamelCase').lower()
【讨论】:
我不明白为什么要同时使用 .sub() 调用? :) 我不是正则表达式大师,但我将功能简化为这个,适合我的某些需求,我只需要一个解决方案将 camelCasedVars 从 POST 请求转换为 vars_with_underscore:
def myFunc(...):
return re.sub('(.)([A-Z]{1})', r'\1_\2', "iTriedToWriteNicely").lower()
它不适用于像 getHTTPResponse 这样的名称,因为我听说它的命名约定不好(应该像 getHttpResponse,很明显,它更容易记住这种形式)。
【讨论】:
'HTTPConnectionFactory',您的代码会产生'h_tt_pconnection_factory',接受答案的代码会产生'http_connection_factory'
这是我的解决方案:
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)
它支持 cmets 中讨论的那些极端情况。例如,它会将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'
【讨论】:
str.join 已被弃用 ages。请改用''.join(..)。
使用正则表达式可能是最短的,但这种解决方案更具可读性:
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
【讨论】:
这不是一个优雅的方法,是一个简单状态机(位域状态机)的非常“低级”的实现,可能是解决这个问题的最反 Python 模式,但是 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
希望有用
【讨论】:
这么多复杂的方法... 只需找到所有“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]*) 掩码。
【讨论】:
一个使用正则表达式的可怕示例(您可以轻松清理它:)):
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")
编辑:也应该很容易看出像“测试”这样的情况还有改进的空间,因为下划线是无条件插入的。
【讨论】:
改编自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')
【讨论】:
这个简单的方法应该可以完成工作:
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()
【讨论】:
这是我为更改制表符分隔文件的标题所做的事情。我省略了我只编辑文件第一行的部分。你可以很容易地使用 re 库来适应 Python。这还包括分离数字(但将数字保持在一起)。我分两步完成,因为这比告诉它不要在行或制表符的开头添加下划线更容易。
第一步...查找大写字母或小写字母前面的整数,并在它们前面加上下划线:
搜索:
([a-z]+)([A-Z]|[0-9]+)
替换:
\1_\l\2/
第二步...执行上述操作并再次运行它以将所有大写字母转换为小写:
搜索:
([A-Z])
替换(即反斜杠,小写L,反斜杠,一):
\l\1
【讨论】:
我一直在寻找解决同一问题的方法,但我需要一条链条;例如
"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
【讨论】:
this site 提出的非常好的 RegEx:
(?<!^)(?=[A-Z])
如果python有一个String Split方法,它应该可以工作......
在 Java 中:
String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
【讨论】:
以防万一有人需要转换完整的源文件,这里有一个脚本可以做到。
# 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 sn-ps 偷了这个。参考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'
【讨论】:
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()
【讨论】:
不在标准库中,但我发现 this module 似乎包含您需要的功能。
【讨论】:
如果您使用Google's (nearly) deterministic Camel case algorithm,则不需要处理HTMLDocument 之类的东西,因为它应该是HtmlDocument,那么这种基于正则表达式的方法很简单。它将所有大写字母或数字替换为下划线。注意不处理多位数字。
import re
def to_snake_case(camel_str):
return re.sub('([A-Z0-9])', r'_\1', camel_str).lower().lstrip('_')
【讨论】:
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 中)转换为大写字母并返回整个字符串。
示例: 命令:“你好”.capitalize() 输出:你好
【讨论】: