Как я могу упростить это преобразование с подчеркивания на camelcase в Python?

Я написал нижеприведенную функцию, которая преобразует подчеркивание в верблюд с первым словом в нижнем регистре, то есть "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

Я чувствую код выше, как написано в непитоновом стиле, хотя он работает так, как ожидалось, поэтому, глядя, как упростить код и написать его с помощью методов списка и т.д.

Ответ 1

Ваш код в порядке. Проблема, которую я пытаюсь решить, заключается в том, что if first_word_passed выглядит немного уродливо.

Один из вариантов для этого - генератор. Мы можем легко сделать это, возвращая одну вещь для первой записи, а другую для всех последующих записей. Поскольку Python имеет первоклассные функции, мы можем заставить генератор вернуть функцию, которую мы хотим использовать для обработки каждого слова.

Затем нам просто нужно использовать условный оператор, чтобы мы могли обрабатывать пустые записи, возвращаемые двойными символами подчеркивания, в понимании списка.

Итак, если у нас есть слово, мы вызываем генератор, чтобы заставить функцию использовать для установки случая, и если мы этого не делаем, мы просто используем _, оставляя генератор нетронутым.

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

Ответ 2

Этот файл работает, за исключением того, что первое слово имеет нижний регистр.

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

(Я знаю, что это не совсем то, о чем вы просили, и эта тема довольно старая, но поскольку она довольно заметна при поиске таких конверсий в Google, я думал, что добавлю свое решение, если это поможет кому-то еще).

Ответ 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

Ответ 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

Я думаю, что код в порядке. У вас довольно сложная спецификация, поэтому, если вы настаиваете на том, чтобы раздавить ее в прокрустово ложе из понимания списка, тогда вы, вероятно, повредите ясность кода.

Единственные изменения, которые я внес бы, будут:

  • Использовать метод join для построения результата в O (n) пространстве и времени, а не для повторных приложений +=, которые являются O (n²).
  • Чтобы добавить docstring.

Вот так:

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

В зависимости от приложения другое изменение, которое я бы рассмотрел, было бы для memoize функции. Я предполагаю, что вы автоматически переводите исходный код каким-то образом, и вы ожидаете, что одни и те же имена будут встречаться много раз. Таким образом, вы можете хранить конверсию, а не переучитывать ее каждый раз. Простым способом сделать это будет использование декоратора @memoized из библиотеки декораторов Python.

Ответ 6

Я согласен с Гаретом в том, что код в порядке. Однако, если вам действительно нужен более короткий, но читаемый подход, вы можете попробовать что-то вроде этого:

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)

Ответ 7

Проблема вызывает функцию, которая возвращает первое строчное слово в первый раз, но потом заглавные слова. Вы можете сделать это с помощью предложения if, но тогда предложение if должно оцениваться для каждого слова. Привлекательной альтернативой является использование генератора. Он может вернуть одну вещь при первом вызове и что-то еще при последовательных вызовах, и для этого не требуется столько if s.

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

Ответ 8

Этот алгоритм хорошо работает с цифрой:

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'

Ответ 9

Вот выражение генератора стиля выражения.

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 )

Ответ 10

Здесь моя, полагаясь главным образом на понимание списка, разделение и объединение. Плюс дополнительный параметр для использования разного разделителя:

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

Ответ 11

Это самый компактный способ сделать это:

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

Ответ 12

Для regexp sake!

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]

Ответ 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()

Он работает для ваших примеров, но может быть неудачным для имен, содержащих цифры - это зависит от того, как вы могли бы их использовать.

Ответ 14

Немного измененная версия:

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)