Выбор между различными заменами switch-case в Python - Dictionary или if-elif-else?

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

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

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

Вот конкретная проблема:
(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-слотом в ответ на выбор рисования кривой из меню. Вышеупомянутый метод будет содержать в общей сложности 4-7 кривых после завершения заявки. Оправдано ли в этом случае использование одноразового словаря? Поскольку наиболее очевидный способ сделать это - if-elif-else, следует ли мне придерживаться этого? Я также подумал об использовании ** kargs здесь (с помощью друзей), поскольку все классы кривых используют ** kargs ...

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

Вот вышеупомянутый слот-

def propertyChanged(self, name, value):
    """A Qt slot, to react to changes of SineCurve's 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 , когда использовать каждый из других вариантов?

Ответов (7)

Решение

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

Проблема заключается в значении, а не в выборе технических утверждений.

Есть три общих шаблона.

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

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

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

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

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

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

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

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

  1. Вы обнаружите (используя, например, модуль профиля), что оператор if является узким местом (очень маловероятно, IMO, если у вас нет огромного количества случаев, которые очень мало делают)

  2. Код, использующий словарь, более понятен / имеет меньше повторений.

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

setattr(self, name, value)

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

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

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

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

./Алекс

По поводу вопросов из словаря:

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

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

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

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

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

Мое практическое правило - спрашивать: «Что здесь происходит?» для каждого компонента моего кода. Если ответ имеет вид

... и ... и ...

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

Я согласен с 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])

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

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

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

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

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

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

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

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