Рефакторинг кода Python, когда единственное, что меняется, — это методы?

#python #refactoring

Вопрос:

Нужен совет о том, как провести рефакторинг такого рода кода, как вы можете видеть, базовый код для всех, слева, справа один и тот же, все, что меняется, — это .strip (),. lstrip (),. rstrip (), можно ли провести рефакторинг «красивым» способом?

 def clean_whitespaces(input, mode='all', ):
    result = None
    modes = (None, 'all', 'left', 'right')
    if mode not in modes:
        raise ValueError('clean_whitespaces: mode must be one of {}.'.format(modes))

    if mode == None:
        mode = 'all'

    if mode == 'all':
        if isinstance(input, str):
            result = input.strip()
        else:
            input = list(input)
            result = [entry.strip() for entry in input]
        return result

    elif mode == 'left':
        if isinstance(input, str):
            result = input.lstrip()
        else:
            input = list(input)
            result = [entry.lstrip() for entry in input]
        return result

    elif mode == 'right':
        if isinstance(input, str):
            result = input.rstrip()
        else:
            input = list(input)
            result = [entry.rstrip() for entry in input]
        return result
 

Комментарии:

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

2. Согласитесь с использованием приведенных выше словарей. Вашими вариантами значений словаря будут либо сами методы (например entry.strip ), либо их имена (например 'strip' ), которые затем можно будет просмотреть с помощью getattr .

3. Используйте «getattr», см. мой ответ, пожалуйста

4. ну, оба приведенных ниже ответа сами по себе «красивы», трудно решить, какой стиль (дикт или лямбда), есть ли какая-либо разница в том, «делать и не делать»

5. в опции лямбда вы все равно повторяете 3 раза, используя «getattr», вам не нужно повторять снова и снова

Ответ №1:

Для этого вы можете использовать диктант:

 modes_to_func = {'all': str.strip, 'left': str.lstrip, 'right': str.rstrip}
 

Таким образом, вы можете избежать повторения modes :

 def clean_whitespaces(input, mode='all', ):
    modes = (None, 'all', 'left', 'right')
    modes_to_func = {'all': str.strip, 'left': str.lstrip, 'right': str.rstrip}
    if mode not in modes:
        raise ValueError('clean_whitespaces: mode must be one of {}.'.format(modes))

    if mode is None:
        mode = 'all'

    if isinstance(input, str):
        result = modes_to_func[mode](input)

    else:
        input = list(input)
        result = [modes_to_func[mode](entry) for entry in input]
    return result
 

Ответ №2:

Так что, очевидно, вы можете сделать что-то подобное str.strip(string_to_strip) (вроде как имеет смысл, потому что вы просто передаете экземпляр ( self )), что позволяет это сделать (с несколькими тестовыми примерами в конце).:

 def clean_whitespaces(data, mode='all'):
    modes = {None: str.strip, 'all': str.strip,
             'left': str.lstrip, 'right': str.rstrip}
    if mode not in modes:
        raise ValueError(f'clean_whitespaces: mode must be one of '
                         f'{", ".join(map(repr, modes.keys()))}.')

    if isinstance(data, str):
        result = modes[mode](data)
    else:
        result = [modes[mode](str(entry)) for entry in list(data)]
    return result


case = '                      super long spaces before                           '
correct_cases = [
    'super long spaces before',
    'super long spaces before',
    'super long spaces before                           ',
    '                      super long spaces before'

]
for m, c in zip((None, 'all', 'left', 'right'), correct_cases):
    assert clean_whitespaces(case, 'w') == c
print('passed assertion')
 

Примечание: по какой-то причине PyCharm это не нравится из-за какого-то неожиданного аргумента, хотя прямое выполнение этого не вызывает такого предупреждения

Ответ №3:

Я бы использовал словарь и переместил весь избыточный код из подфункции в основную функцию.

 def clean_whitespace(inp, mode='all'):

    def allSelected(x):
        return x.strip()

    def rightSelected(x):
        return x.rstrip()        
   
    def leftSelected(x):
        return x.lstrip()

    mdict = {'all': allSelected , 'left': leftSelected, 'right': rightSelected} 
    if mode == None:
        mode = 'all'
    try:
        if isinstance(inp, str):
            return mdict[mode](inp)
        else:
            return [mdict[mode](entry) for entry in inp]
    except KeyError:
        print('clean_whitespaces: mode must be one of {}.'.format([mdict.keys()]))
 

Комментарии:

1. в def leftSelected(inp): return x.lstrip() inp есть опечатка, она должна быть x, верно?

2. Обновлено, спасибо за улов

Ответ №4:

Вы можете переместить дубликат кода в функцию и настроить его с помощью лямбда:

 def strip(strip_foo, input):
    if isinstance(input, str):
        result = strip_foo(input)
    else:
        input = list(input)
        result = [strip_foo(entry) for entry in input]
    return result


def clean_whitespaces(input, mode='all', ):
    result = None
    modes = (None, 'all', 'left', 'right')
    if mode not in modes:
        raise ValueError('clean_whitespaces: mode must be one of {}.'.format(modes))

    if mode == None:
        mode = 'all'

    if mode == 'all':
        return strip(lambda v: v.strip(), input)
    elif mode == 'left':
        return strip(lambda v: v.lstrip(), input)
    elif mode == 'right':
        return strip(lambda v: v.rstrip(), input)
 

Ответ №5:

Итак, просмотрев все ответы, которые я придумал, это более или менее объединение 3 ответов с диктами, спасибо всем вам за ваши подсказки (например, ввод)

 def clean_whitespaces(data, mode='all', ):
result = None
modes = {
    'all': str.strip,
    'left': str.lstrip,
    'right': str.rstrip
    }

if mode not in modes:
    raise ValueError('clean_whitespaces: mode must be one of {}.'.format([key for key in modes.keys()]))

if isinstance(data, str):
    result = modes[mode](data)
else:
    data = list(data)
    result = [modes[mode](entry) for entry in data]
    return result
 

Комментарии:

1. вы должны отменить return привязку , в противном случае это будет return result только в том случае, если data это не строка, и в этом result = None нет необходимости, вы всегда будете получать что-то в результате, в качестве альтернативы вы можете сделать что-то подобное return result or None , если вы хотите явно вернуть None , например result , если это пустой список

2. да, верно, спасибо, я думаю, что это было копирование и вставка ^^