Выбор между различными заменами ключей в 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, когда использовать каждый из других параметров?
Ответы
Ответ 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".