【问题标题】:How can I simplify this conversion from underscore to camelcase in Python?如何在 Python 中简化从下划线到驼峰式的转换?
【发布时间】:2010-11-29 12:00:44
【问题描述】:

我编写了下面的函数,它将下划线转换为驼峰式,第一个单词为小写,即“get_this_value”->“getThisValue”。我还要求保留前导和尾随下划线以及双(三等)下划线(如果有的话),即

"_get__this_value_" -> "_get_ThisValue_".

代码:

def underscore_to_camelcase(value):
    output = ""
    first_word_passed = False
    for word in value.split("_"):
        if not word:
            output += "_"
            continue
        if first_word_passed:
            output += word.capitalize()
        else:
            output += word.lower()
        first_word_passed = True
    return output

我感觉上面的代码是用非 Python 风格编写的,尽管它按预期工作,所以看看如何简化代码并使用列表推导等编写它。

【问题讨论】:

  • 你的问题不清楚;你想把“get_this_value”转换成“getThisValue”还是“get_ThisValue
  • "get_this_value" -> "getThisValue",第一个变体。没有下划线,除非它们在开头、结尾或两个或多个下划线相互跟随。
  • 您确定您的功能按预期工作吗?你是说你想 preserve 双,三…下划线,但是你的代码正在做的是剪掉一个下划线。 IE。从 get__this_value 得到 get_ThisValue,从 get____this 得到 get___This。这是你想要的吗?

标签: python


【解决方案1】:

除了将第一个单词保留为小写之外,此方法有效。

def convert(word):
    return ''.join(x.capitalize() or '_' for x in word.split('_'))

(我知道这不完全是您所要求的,而且该线程已经很老了,但是由于在 Google 上搜索此类转换时它非常突出,我想我会添加我的解决方案以防它对其他人有所帮助) .

【讨论】:

  • 这太棒了。对于新手 Python 用户,如果您的单词被任何一个符号(在我的情况下是空格)分隔,您可以在两个位置替换 _ 以使用此功能在其他情况下为您提供驼峰式大小写。 +1
  • return word.split('_')[0] + ''.join(x.capitalize() or '_' for x in word.split('_')[1:)
  • @TimothyAaron 语法修复 -- return word.split('_')[0] + ''.join(x.capitalize() or '_' for x in word.split('_')[1:]) -- 缺少最后的 ]
  • 为什么需要or '_'
【解决方案2】:

您的代码很好。我认为您要解决的问题是if first_word_passed 看起来有点丑。

解决此问题的一个选项是生成器。我们可以很容易地让第一个条目返回一个东西,然后为所有后续条目返回另一个东西。由于 Python 有一流的函数,我们可以让生成器返回我们想要用来处理每个单词的函数。

然后我们只需要使用the conditional operator,这样我们就可以在列表解析中处理由双下划线返回的空白条目。

因此,如果我们有一个单词,我们调用生成器来获取用于设置大小写的函数,如果没有,我们只需使用 _ 保持生成器不变。

def underscore_to_camelcase(value):
    def camelcase(): 
        yield str.lower
        while True:
            yield str.capitalize

    c = camelcase()
    return "".join(c.next()(x) if x else '_' for x in value.split("_"))

【讨论】:

  • 不错,但 FWIW str.lower 和 str.capitalize 不需要导入。
  • @freegnu - 谢谢。已更新代码以使用 str.lowerstr.capitalize
  • 使用str. 使其不适用于 unicode 对象。尝试将str 替换为type(value)
【解决方案3】:

我个人更喜欢正则表达式。这是对我有用的一个:

import re
def to_camelcase(s):
    return re.sub(r'(?!^)_([a-zA-Z])', lambda m: m.group(1).upper(), s)

使用unutbu 的测试:

tests = [('get__this_value', 'get_ThisValue'),
         ('_get__this_value', '_get_ThisValue'),
         ('_get__this_value_', '_get_ThisValue_'),
         ('get_this_value', 'getThisValue'),
         ('get__this__value', 'get_This_Value')]

for test, expected in tests:
    assert to_camelcase(test) == expected

【讨论】:

  • 这是一个很好的解决方案,因为它不会弄乱驼峰式的字符串。
  • 它把 SCRIPT_CUSTOM 改为 SCRIPTCUSTOM 为什么?其他一切似乎都正常
  • 从技术上讲,这是按预期工作的:它删除了下划线并确保以下字母大写。如果您正在寻找 SCRIPT_CUSTOM 成为 scriptCustom,只需使用 .lower() 传递它:to_camelcase('SCRIPT_CUSTOM'.lower())
【解决方案4】:

这里有一个更简单的。可能并不适合所有情况,但它符合我的要求,因为我只是将具有特定格式的 python 变量转换为驼峰式。这确实将除第一个单词之外的所有单词都大写。

def underscore_to_camelcase(text):
"""
Converts underscore_delimited_text to camelCase.
Useful for JSON output
"""
    return ''.join(word.title() if i else word for i, word in enumerate(text.split('_')))

【讨论】:

    【解决方案5】:

    我认为代码很好。您有一个相当复杂的规范,因此如果您坚持将其压缩到列表理解的 Procrustean 床中,那么您可能会损害代码的清晰度。

    我要做的唯一改变是:

    1. 使用join方法在O(n)个空间和时间构建结果,而不是重复应用+=,即O(n ²)。
    2. 添加文档字符串。

    像这样:

    def underscore_to_camelcase(s):
        """Take the underscore-separated string s and return a camelCase
        equivalent.  Initial and final underscores are preserved, and medial
        pairs of underscores are turned into a single underscore."""
        def camelcase_words(words):
            first_word_passed = False
            for word in words:
                if not word:
                    yield "_"
                    continue
                if first_word_passed:
                    yield word.capitalize()
                else:
                    yield word.lower()
                first_word_passed = True
        return ''.join(camelcase_words(s.split('_')))
    

    根据应用程序,我会考虑进行的另一个更改是记忆函数。我假设您正在以某种方式自动翻译源代码,并且您希望相同的名称多次出现。因此,您不妨存储转换而不是每次都重新计算。一个简单的方法是使用来自Python decorator library@memoized 装饰器。

    【讨论】:

      【解决方案6】:

      这个算法对数字表现很好:

      import re
      
      PATTERN = re.compile(r'''
          (?<!\A) # not at the start of the string
          _
          (?=[a-zA-Z]) # followed by a letter
          ''', re.X)
      
      def camelize(value):
          tokens = PATTERN.split(value)
          response = tokens.pop(0).lower()
          for remain in tokens:
              response += remain.capitalize()
          return response
      

      例子:

      >>> camelize('Foo')
      'foo'
      >>> camelize('_Foo')
      '_foo'
      >>> camelize('Foo_')
      'foo_'
      >>> camelize('Foo_Bar')
      'fooBar'
      >>> camelize('Foo__Bar')
      'foo_Bar'
      >>> camelize('9')
      '9'
      >>> camelize('9_foo')
      '9Foo'
      >>> camelize('foo_9')
      'foo_9'
      >>> camelize('foo_9_bar')
      'foo_9Bar'
      >>> camelize('foo__9__bar')
      'foo__9_Bar'
      

      【讨论】:

        【解决方案7】:

        这是我的,主要依靠列表理解、拆分和连接。加上可选参数以使用不同的分隔符:

        def underscore_to_camel(in_str, delim="_"):
            chunks = in_str.split(delim)
            chunks[1:] = [_.title() for _ in chunks[1:]]
            return "".join(chunks)
        

        另外,为了完整起见,包括前面提到的作为反向的另一个问题的解决方案(不是我自己的代码,只是为了方便参考而重复):

        first_cap_re = re.compile('(.)([A-Z][a-z]+)')
        all_cap_re = re.compile('([a-z0-9])([A-Z])')
        def camel_to_underscore(in_str):
            s1 = first_cap_re.sub(r'\1_\2', name)
            return all_cap_re.sub(r'\1_\2', s1).lower()
        

        【讨论】:

          【解决方案8】:

          我同意 Gareth 的观点,即代码没问题。但是,如果您真的想要一个更短但可读的方法,您可以尝试这样的方法:

          def underscore_to_camelcase(value):
              # Make a list of capitalized words and underscores to be preserved
              capitalized_words = [w.capitalize() if w else '_' for w in value.split('_')]
          
              # Convert the first word to lowercase
              for i, word in enumerate(capitalized_words):
                  if word != '_':
                      capitalized_words[i] = word.lower()
                      break
          
              # Join all words to a single string and return it
              return "".join(capitalized_words)
          

          【讨论】:

            【解决方案9】:

            这个问题需要一个函数,它第一次返回一个小写单词,然后返回大写单词。您可以使用if 子句来做到这一点,但是必须为每个单词评估if 子句。一个吸引人的替代方案是使用生成器。它可以在第一次调用时返回一个内容,在后续调用中返回其他内容,并且不需要那么多 ifs。

            def lower_camelcase(seq):
                it=iter(seq)
                for word in it:
                    yield word.lower()
                    if word.isalnum(): break
                for word in it:
                    yield word.capitalize()
            
            def underscore_to_camelcase(text):
                return ''.join(lower_camelcase(word if word else '_' for word in text.split('_')))
            

            这里有一些测试代码来证明它可以工作:

            tests=[('get__this_value','get_ThisValue'),
                   ('_get__this_value','_get_ThisValue'),
                   ('_get__this_value_','_get_ThisValue_'),
                   ('get_this_value','getThisValue'),        
                   ('get__this__value','get_This_Value'),        
                   ]
            for test,answer in tests:
                result=underscore_to_camelcase(test)
                try:
                    assert result==answer
                except AssertionError:
                    print('{r!r} != {a!r}'.format(r=result,a=answer))
            

            【讨论】:

              【解决方案10】:

              这是一个列表推导式生成器表达式。

              from itertools import count
              def underscore_to_camelcase(value):
                  words = value.split('_')
                  counter = count()
                  return ''.join('_' if w == '' else w.capitalize() if counter.next() else w for w in words )
              

              【讨论】:

                【解决方案11】:
                    def convert(word):
                        if not isinstance(word, str):
                            return word
                        if word.startswith("_"):
                            word = word[1:]
                        words = word.split("_")
                        _words = []
                        for idx, _word in enumerate(words):
                            if idx == 0:
                                _words.append(_word)
                                continue
                            _words.append(_word.capitalize())
                        return ''.join(_words)
                

                【讨论】:

                  【解决方案12】:

                  这是最简洁的方式:

                  def underscore_to_camelcase(value):
                      words = [word.capitalize() for word in value.split('_')]
                      words[0]=words[0].lower()
                      return "".join(words)
                  

                  【讨论】:

                  • 这不符合规范! (对于_get_this_value,它返回GetThisValue,而_getThisValue 是需要的。)
                  【解决方案13】:

                  另一种正则表达式解决方案:

                  import re
                  
                  def conv(s):
                      """Convert underscore-separated strings to camelCase equivalents.
                  
                      >>> conv('get')
                      'get'
                      >>> conv('_get')
                      '_get'
                      >>> conv('get_this_value')
                      'getThisValue'
                      >>> conv('__get__this_value_')
                      '_get_ThisValue_'
                      >>> conv('_get__this_value__')
                      '_get_ThisValue_'
                      >>> conv('___get_this_value')
                      '_getThisValue'
                  
                      """
                      # convert case:
                      s = re.sub(r'(_*[A-Z])', lambda m: m.group(1).lower(), s.title(), count=1)
                      # remove/normalize underscores:
                      s = re.sub(r'__+|^_+|_+$', '|', s).replace('_', '').replace('|', '_')
                      return s
                  
                  if __name__ == "__main__":
                      import doctest
                      doctest.testmod()
                  

                  它适用于您的示例,但对于包含数字的名称可能会失败 - 这取决于您如何将它们大写。

                  【讨论】:

                  • @freegnu:你能说得更准确点吗?
                  • @'Oben Sonne' 尽管请求者给出的示例删除了连续的下划线,但请求的规范要求保留双下划线和三重下划线。请求者给出的例子可能是错误的。
                  • @freegnu:嗯,您对一个模棱两可的问题有您的具体解释,并基于此将我的回答标记为错误?我的回答是建议如何解决 OP 的问题,并且文档测试清楚地传达了它的工作规范(保留双下划线和三重下划线解释为将它们压缩为一个)。所以有什么问题?批评答案时,请首先放松并多沟通。
                  【解决方案14】:

                  为了正则表达式!

                  import re
                  
                  def underscore_to_camelcase(value):
                      def rep(m):
                          if m.group(1) != None:
                              return m.group(2) + m.group(3).lower() + '_'
                          else:
                              return m.group(3).capitalize()
                  
                      ret, nb_repl = re.subn(r'(^)?(_*)([a-zA-Z]+)', rep, value)
                      return ret if (nb_repl > 1) else ret[:-1]
                  

                  【讨论】:

                  • 不符合规范!对于get__this_value,它返回getThisValue,而get_ThisValue 是需要的。
                  • 再次修复(请去掉系统感叹号,这很不愉快)。
                  【解决方案15】:

                  稍作修改的版本:

                  import re
                  
                  def underscore_to_camelcase(value):
                      first = True
                      res = []
                  
                      for u,w in re.findall('([_]*)([^_]*)',value):
                          if first:
                              res.append(u+w)
                              first = False
                          elif len(w)==0:    # trailing underscores
                              res.append(u)
                          else:   # trim an underscore and capitalize
                              res.append(u[:-1] + w.title())
                  
                      return ''.join(res)
                  

                  【讨论】:

                    【解决方案16】:

                    我知道这已经被回答了,但我想出了一些句法糖来处理所选答案没有的特殊情况(其中带有 dunders 的单词,即“my_word__is_____ugly”到“myWordIsUgly”)。显然,这可以分成多行,但我喜欢将它放在一个上的挑战。为了清楚起见,我添加了换行符。

                    def underscore_to_camel(in_string):
                    return "".join(
                        list(
                            map(
                                lambda index_word:
                                    index_word[1].lower() if index_word[0] == 0
                                    else index_word[1][0].upper() + (index_word[1][1:] if len(index_word[1]) > 0 else ""),
                                list(enumerate(re.split(re.compile(r"_+"), in_string)
                                               )
                                     )
                            )
                        )
                    )
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-07-08
                      • 2015-05-01
                      • 2016-10-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-08-07
                      • 1970-01-01
                      相关资源
                      最近更新 更多