Подтвердить что ты не робот

Выбор между различными заменами ключей в Python - словаре или if-elif-else?

Недавно я прочитал вопросы, которые рекомендуют против использования операторов switch-case на языках, которые его поддерживают. Что касается Python, я видел несколько замен по замене коммутатора, например:

  • Использование словаря (многие варианты)
  • Использование кортежа
  • Использование декоратора функций (http://code.activestate.com/recipes/440499/)
  • Использование полиморфизма (рекомендуемый метод вместо объектов проверки типов)
  • Использование лестницы if-elif-else
  • Кто-то даже рекомендовал шаблон посетителя (возможно, Extrinsic)

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

Вот конкретная проблема:
(1)

def _setCurrentCurve(self, curve):
        if curve == "sine":
            self.currentCurve =  SineCurve(startAngle = 0, endAngle = 14,
            lineColor = (0.0, 0.0, 0.0), expansionFactor = 1,
            centerPos = (0.0, 0.0))
        elif curve == "quadratic":
            self.currentCurve = QuadraticCurve(lineColor = (0.0, 0.0, 0.0))

Этот метод вызывается qt-slot в ответ на выбор рисования кривой из меню. Вышеуказанный метод будет содержать в общей сложности 4-7 кривых, как только приложение будет завершено. В этом случае оправданно использовать броский словарь? Поскольку самый очевидный способ сделать это - если-elif-else, я должен придерживаться этого? Я также рассматриваю использование ** kargs здесь (с помощью друзей), так как все классы кривой используют ** kargs...

(2)
Эта вторая часть кода представляет собой qt-slot, который вызывается, когда пользователь меняет свойство кривой. В основном слот берет данные из gui (spinBox) и помещает его в переменную экземпляра соответствующего класса кривой. В этом случае у меня снова есть тот же вопрос - следует ли использовать dict?

Вот вышеназванный слот

def propertyChanged(self, name, value):
    """A Qt slot, to react to changes of SineCurve properties."""
    if name == "amplitude":
        self.amplitude = value
    elif name == "expansionFactor":
        self.expansionFactor = value
    elif name == "startAngle":
        self.startAngle = value
    elif name == "endAngle":
        self.endAngle = value  

Для справки, вот код для подключения к вышеуказанному слоту -

def _connectToPage(self, page):
    for connectionData in page.getConnectibles():
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    lambda value, name = connectionData["property"]:\
                        self.currentCurve.propertyChanged(name, value))
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    self.hackedDisplayArea.update) 

Примечание. В конструкторе инициализируются self.endAngle и т.д.

Насколько я знаю, причины выбора dict - это быстрый поиск. Когда это оправдано? когда у меня есть 100 случаев или больше? Это хорошая идея продолжать строить и отбрасывать словарь каждый раз, когда вызывается функция? Если я построю dict для этой цели вне функции, должен ли я проверить, нужно ли это где-нибудь? Что произойдет, если оно не понадобится в другом месте?

Мой вопрос в том, что является лучшей практикой, если он есть? Каков лучший/самый элегантный способ заниматься вещами? Поставить еще один способ, когда использовать if-elif-else, когда использовать каждый из других параметров?

4b9b3361

Ответ 1

Вздох. Слишком много рук, сжимающих неправильную часть проблемы. Вывод switch не является проблемой. Существует много способов выражения "альтернативы", которые не добавляют значение.

Проблема означает - не технические решения.

Существует три общих шаблона.

  • Отображение ключа для объекта. Используйте словарь, если он почти полностью статичен, и у вас есть сопоставление между простым ключом и другой более сложной вещью. Создание словаря "на лету" каждый раз, когда вам это нужно, глупо. Вы можете использовать это, если это то, что вы означают: ваши "условия" просты, статические значения ключей, которые сопоставляются с объектами.

  • Вариант поведения среди подклассов. Используйте полиморфизм вместо объектов проверки типов. Верный. Если у вас есть похожие объекты в нескольких классах с вариантом поведения, они должны быть полиморфными. Используйте это как можно чаще.

  • Другое поведение варианта. Используйте лестницу if-elif-else. Используйте это, если у вас нет в основном статического сопоставления "ключ-к-значению". Используйте это, когда условия сложны, или означают процедуры, а не объекты.

Все остальное - всего лишь сложный код, который может достичь аналогичных результатов.

Использование кортежа. Это просто словарь без отображения. Это требует поиска, и поиск следует избегать, когда это возможно. Не делайте этого, это неэффективно. Используйте словарь.

Использование декоратора функции (http://code.activestate.com/recipes/440499/). Icky. Это скрывает природу проблемы if-elif-elif, которую вы решаете. Не делайте этого, не очевидно, что выбор является исключительным. Используйте что-нибудь еще.

Кто-то даже рекомендовал шаблон Посетитель. Используйте это, когда у вас есть объект, который следует за шаблоном дизайна Композитный. Это зависит от того, полиморфизм работать, поэтому это не совсем другое решение.

Ответ 2

В первом примере я бы обязательно придерживался инструкции if-else. На самом деле я не вижу причины не использовать if-else, если

  • Вы обнаруживаете (используя, например, модуль профиля), что оператор if является узким местом (очень маловероятный ИМО, если у вас нет большого количества случаев, которые очень мало)

  • Код с использованием словаря clearer/имеет меньшее повторение.

Второй пример: я бы переписал

setattr(self, name, value)

(возможно, добавление оператора assert для улавливания недействительных имен).

Ответ 3

Учитывая, что это сделано в ответ на действие пользователя (выбирает что-то из меню), и количество вариантов, которые вы ожидаете, очень мало, я бы определенно пошел с простой лестницей if-elif-else.

Нет смысла оптимизировать скорость, поскольку это происходит так же быстро, как пользователь может сделать выбор в любом случае, это не "внутренняя петля raytracer" -территория. Конечно, важно дать пользователю быструю обратную связь, но поскольку количество случаев настолько невелико, это тоже не опасно.

Нет смысла оптимизировать для краткости, поскольку if-лестница (imo clearer, zero-readability-overhead) будет настолько коротким в любом случае.

Ответ 4

Что касается ваших словесных вопросов:

Насколько я знаю, причины выбора dict - это быстрый поиск. Когда это оправдано? когда у меня есть 100 случаев или больше? Это хорошая идея продолжать строить и отбрасывать словарь каждый раз, когда вызывается функция? Если я построю dict для этой цели вне функции, должен ли я проверить, нужно ли это где-нибудь? Что произойдет, если оно не понадобится в другом месте?

  • Другая проблема - ремонтопригодность. Наличие словаря string- > curveFunction позволяет вам управлять данными в меню. Тогда добавление другого параметра - это просто вопрос ввода в строку словаря string- > function (который находится в части кода, посвященного конфигурации.

  • Даже если у вас есть только несколько записей, это "разделяет проблемы"; _setCurrentCurve отвечает за проводку во время выполнения, не определяя поле компонентов.

  • Создайте словарь и удерживайте его.

  • Даже если он не используется в другом месте, вы получаете вышеупомянутые преимущества (локативность, ремонтопригодность).

Мое правило - спросить: "Что здесь происходит?" для каждого компонента моего кода. Если ответ имеет форму

... и... и...

(как в "определении библиотеки функций и, связывающей каждый со значением в меню" ), есть некоторые проблемы, требующие разделения.

Ответ 5

Каждый из выставленных вариантов хорошо соответствует некоторым сценариям:

  • if-elif-else: простота, ясность
  • : полезно, когда вы настраиваете его динамически (представьте, что вам нужна конкретная функциональность, которая должна выполняться на ветке)
  • tuple: простота над случаем if-else для нескольких вариантов выбора для каждой ветки.
  • полиморфизм: автоматическое объектно-ориентированное ветвление
  • и др.

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

./Алекс

Ответ 6

Я согласен с df относительно второго примера. Первый пример, который я, вероятно, попытаюсь переписать с помощью словаря, особенно если все конструкторы кривой имеют одну и ту же подпись типа (возможно, используя * args и/или ** kwargs). Что-то вроде

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](options_here)

или, возможно, даже

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](**preset_curve_defaults[new_curve])

Ответ 7

В Python не думайте о том, как заменить оператор switch.

Вместо этого используйте классы и полиморфизм. Попытайтесь сохранить информацию о каждом доступном выборе и о том, как реализовать его в одном месте (т.е. Класс, который его реализует).

В противном случае у вас будет много мест, каждая из которых содержит крошечную часть каждого выбора, а обновление/продление - кошмар для обслуживания.

Это именно та проблема, которую OOD пытается решить путем абстракции, скрытия информации, полиморфизма и партии.

Подумайте, какие классы объектов у вас есть и их свойства, а затем создайте вокруг них OO-архитектуру. Таким образом, вам больше никогда не придется беспокоиться о отсутствующем заявлении "switch".