def _cc2jl(string):
"""Camel case to joint-lower helper."""
for index, current in enumerate(string):
if is_upper(current):
if index > 0:
previous = string[index-1]
if is_upper(previous):
try:
next = string[index+1]
except IndexError:
yield current.lower()
continue
else:
if is_upper(next):
yield current.lower()
continue
yield '_' + current.lower()
continue
yield current.lower()
continue
yield current
def cc2jl(string):
"""Converts a camel case string to joint-lower."""
return ''.join(_cc2jl(string))
预期的行为:
>>> cc2jl('m')
'm'
>>> cc2jl('AA')
'aa'
>>> cc2jl('MySQLDatabase')
'my_sql_database'
>>> cc2jl('GarbageCollection')
'garbage_collection'
>>> cc2jl('AAAAAAAAAAAAAAAAA')
'aaaaaaaaaaaaaaaaa'
#1 楼
我认为正则表达式将是最容易做到的。您只需要找到字符串中的每个单词。名称以大写字母开头。
[A-Z]
。但是由于正则表达式其余部分的工作方式,我们可以将其更改为.
,以便我们匹配所有单词,即使是以_
开头的单词。该单词将包含大写或小写字母,都带有其他字符-不包括
_
。大写字母:
该单词不会小写或带有
_
。 [^a-z_]+
您不需要最后一个大写字母或
_
。 (?=[A-Z_])
如果它是字符串中的最后一个,则需要上面的内容。
(?=[A-Z_]|$)
小写:
该单词不会大写或带有
_
。 [^A-Z_]+
因此您可以使用:
(.(?:[^a-z_]+(?=[A-Z_]|$)|[^A-Z_]+))
然后您要对它们应用以下内容:
在名称前加上一个
_
,除非:这是名称中的第一个单词
该单词已经以
_
开头将单词转换为小写
使:
def _jl_match(match):
group = match.group()
prefix = bool(match.start() and not group.startswith('_'))
return '_' * prefix + group.lower()
所以我要使用:
def _jl_match(match):
group = match.group()
prefix = bool(match.start() and not group.startswith('_'))
return '_' * prefix + group.lower()
REGEX = r'(.(?:[^a-z_]+(?=[A-Z_]|$)|[^A-Z_]+))'
def _cc2jl(string):
return re.subn(REGEX, _jl_match, string)[0]
#2 楼
有两件事:为什么有2个功能?
现在我们建立了(1),您可以摆脱
yield
。 yield
在您要分批处理物料并且不想等待首先创建整个总体时很有用。在这里不是这种情况。有些
continue
确实有意义,而有些冗余。如果没有降低可读性,可以将带有相应if
子句的嵌套else
语句与and
合并。>
def cc2jl(my_str):
"""Camel case to joint-lower"""
r = my_str[0].lower()
for i, letter in enumerate(my_str[1:], 1):
if letter.isupper():
try:
if my_str[i-1].islower() or my_str[i+1].islower():
r += '_'
except IndexError:
pass
r += letter.lower()
return r
print(cc2jl('m')) #-> m
print(cc2jl('AA')) #-> aa
print(cc2jl('MySQLDatabase')) #-> my_sql_database
print(cc2jl('GarbageCollection')) #-> garbage_collection
print(cc2jl('AAAAAAAAAAAAAAAAA')) #-> aaaaaaaaaaaaaaaaa
最后,一些避免使用
try
块的想法引起了我的困扰。我们可以利用Python的逻辑表达式,并(重新)编写以下内容:
def cc2jl(my_str):
"""Camel case to joint-lower"""
r = my_str[0].lower()
for i, letter in enumerate(my_str[1:], 1):
if letter.isupper():
if my_str[i-1].islower() or (i != len(my_str)-1 and my_str[i+1].islower()):
r += '_'
r += letter.lower()
return r
请注意
i != len(my_str)-1
左侧的and
。如果返回False
,则根本不会评估my_str[i+1]
,因此无法对raise
进行评估。评论
\ $ \ begingroup \ $
清理OP代码很不错!我在答案中将您的函数添加到了时序图中,它比我的regex函数和OP的函数要快(可能是由于摆脱了生成器的开销)。
\ $ \ endgroup \ $
–地狱
18年1月25日在14:07
\ $ \ begingroup \ $
@Graipher我最不喜欢我的方法的是try-except块,但没有想到更优雅的东西。
\ $ \ endgroup \ $
– Ma0
18年1月25日在14:11
\ $ \ begingroup \ $
如果不是0 \ $ \ endgroup \ $
–地狱
18年1月25日在14:15
\ $ \ begingroup \ $
@Graipher 0我不需要,因为我正在遍历my_str [1:]。我尝试遍历my_str [1:-1]并处理返回行中的最后一个字母,但这没什么用更好。总之感谢!
\ $ \ endgroup \ $
– Ma0
18年1月25日在14:17
#3 楼
首先要注意命名:cc2jl
是一个非常隐秘的名称。给公用函数一个更清晰的名称,例如to_snake_case
或类似的名称(请注意,该函数不会对snake_case
中已经存在的字符串执行任何操作)。我很喜欢这个事实您使用了生成器方法,该方法可以确保仅在传递字符串时进行,这似乎是正则表达式的理想之地。一些快速的谷歌搜索发现此功能非常简单:但是遵循代码虽然编写起来非常简单,但也并不容易,尤其是在深层嵌套中。
它的工作原理是先分割大写字母,再分割一个或多个小写字母,以便最后一个大写字母转到小写字母运行。因此,
"ABCdef"
变为"AB_Cdef"
。然后将非大写字母的行分隔开,后面是带有
"_"
的单个大写字母,因此"abcD"
变为"abc_D"
。要从中获得更多性能,您应该预编译正则表达式:import re
def convert(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'_', name)
return re.sub('([a-z0-9])([A-Z])', r'_', s1).lower()
性能方面,所有算法都是相似的: >
不足为奇,它们都具有线性行为(作为字符串长度的函数)。
我的regex版本需要进行两次传递字符串,因此始终较慢。 @ Ev.Kounis在他的答案中编写的函数优于我们两个函数,但是@Peilonrayz的正则表达式方法甚至更快,因为它只能执行一次通过,但是带有正则表达式。
注意所有功能都非常快,因此只要您每秒需要几千次以下就可以了。
测试字符串是使用以下代码生成的:
first_cap_re = re.compile('(.)([A-Z][a-z]+)')
all_cap_re = re.compile('([a-z0-9])([A-Z])')
def convert(name):
s1 = first_cap_re.sub(r'_', name)
return all_cap_re.sub(r'_', s1).lower()
在Python 3中可能是:
import random
import string
strings = [''.join(random.choice(string.ascii_letters) for _ in range(n))
for n in range(10, 2000, 20)]
评论
\ $ \ begingroup \ $
如果我了解正则表达式,我会很喜欢这个答案。我的脑子太小了。 +1
\ $ \ endgroup \ $
– Ma0
18年1月25日在14:08
\ $ \ begingroup \ $
@ Ev.Kounis我完全同意(因为我自己没有提出它,请参阅源链接)。这里至少有一些解释:第一个替换处理连续的大写字母(因此它将大写字母从最后一个大写字母中拆分出来),而最后一个替换将小写字母和大写字母后跟拆分。
\ $ \ endgroup \ $
–地狱
18年1月25日在14:13
\ $ \ begingroup \ $
感谢您的深入分析和性能评估。由于后者,我接受了@peilonrayz解决方案。
\ $ \ endgroup \ $
–理查德·诺伊曼(Richard Neumann)
18年1月26日在9:02
#4 楼
它可以更短一些:def cc2jl(s):
return "".join(["_"+l if i and l.isupper() and not s[i-1:i+2].isupper() else l for i, l in enumerate(s)]).lower()
正则表达式替代项:
rx = re.compile(r"(?<=.)(((?<![A-Z])[A-Z])|([A-Z](?=[a-z])))")
def cc2jl(s):
return rx.sub("_\1", s).lower()
评论
\ $ \ begingroup \ $
我的观察结果是代码长于所需的事实,我的答案是替代解决方案。与正则表达式相比,可读性是主观的,我认为它更易于阅读。
\ $ \ endgroup \ $
– Utku M. ALTINKAYA
18年1月26日在6:14
\ $ \ begingroup \ $
@Graipher已修复
\ $ \ endgroup \ $
– Utku M. ALTINKAYA
18年1月26日在8:06
\ $ \ begingroup \ $
在计时中添加了功能
\ $ \ endgroup \ $
–地狱
18年1月26日在9:12
\ $ \ begingroup \ $
“(...)与regex相比,我认为它更易于阅读”。不
\ $ \ endgroup \ $
– Grajdeanu Alex
18年1月26日在11:24
\ $ \ begingroup \ $
感谢@Graipher,我已经修改了该代码并添加了正则表达式替代项
\ $ \ endgroup \ $
– Utku M. ALTINKAYA
18年1月26日在12:11
评论
\ $ \ begingroup \ $
很好,我知道应该有一种方法可以处理单个正则表达式,但是我的正则表达式太弱了,无法提出一个...我在时序图中添加了您的功能,这就是最快(到目前为止)。我确实对其进行了修改,以便首先编译正则表达式。
\ $ \ endgroup \ $
–地狱
18年1月25日在19:50
\ $ \ begingroup \ $
这是可以使用的各种正则表达式的比较。
\ $ \ endgroup \ $
– 200_success
18年1月25日在19:52
\ $ \ begingroup \ $
因为它的简单性和性能,我选择了您的解决方案:github.com/HOMEINFO/strflib/commit/…
\ $ \ endgroup \ $
–理查德·诺伊曼(Richard Neumann)
18年1月26日在9:02
\ $ \ begingroup \ $
@RichardNeumann谢谢你。但这不是您违反的BY部分-我很高兴您没有:)这是SA部分。如果SE使用CC-BY-SA 4.0,并且您已许可使用GPLv3,则不会有任何违规行为。但他们使用CC-BY-SA 3.0,):
\ $ \ endgroup \ $
– Peilonrayz
18年1月26日,11:33
\ $ \ begingroup \ $
@RichardNeumann很好。我想知道您是否也可以这样做... IANAL,所以我想是时候去Law.SE,; P
\ $ \ endgroup \ $
– Peilonrayz
18年1月26日在12:07