Question:
Faced with the fact that it is required to implement a multiple condition, which in other languages I would implement using the switch-case
construct.
In Python, I have to write everything through if-elif-else
. This seems rather inconvenient to me.
Is there a better way to write such conditions?
For example, I have units of measurement and, depending on the selected one, I need to return the appropriate multiplier:
def get_multiplier(unit):
if unit == 'mm':
return 10**-3
if unit == 'cm':
return 10**-2
if unit == 'dm':
return 10**-1
if unit == 'm':
return 1
if unit == 'km':
return 10**3
raise ValueError('Undefined unit: {}'.format(unit))
Answer:
For starters, there is nothing particularly wrong with using the if-elif-else
.
Several alternatives can be found if desired.
Using dictionaries
A fairly common way to organize a switch-case
construct in Python is to use a dictionary. It's easier to show with an example:
unit_to_multiplier = {
'mm': 10**-3,
'cm': 10**-2,
'dm': 10**-1,
'm': 1,
'km': 10**3
}
In order to get the desired multiplier in this case, you only need to take the value by key:
try:
mult = unit_to_multiplier['cm']
except KeyError as e:
# можно также присвоить значение по умолчанию вместо бросания исключения
raise ValueError('Undefined unit: {}'.format(e.args[0]))
If you're pretty sure the value will always be in the dictionary, you can omit the try-except
block and be prepared to catch the exception elsewhere.
A variation on this approach would be to pre-check the value in the condition:
if unit in unit_to_multiplier:
mult = unit_to_multiplier[unit]
else:
# обработка отсутствия значения в словаре
In Python, it's common to take an approach that sounds something like "it's better to try and get an error than to ask permission every time", so the exception approach is more preferable.
If you want to use a default value in case the key is missing, it's convenient to use the get
method:
mult = unit_to_multiplier.get('ultra-meter', 0)
If you only need the dictionary once, you can combine these expressions into one:
unit_to_multiplier = {
'mm': 10**-3,
'cm': 10**-2,
'dm': 10**-1,
'm': 1,
'km': 10**3
}.get('km', 0)
The possibilities of this approach do not end there. You can use conditional expressions as dictionary keys:
def get_temp_description(temp):
return {
temp < -20: 'Холодно',
-20 <= temp < 0: 'Прохладно',
0 <= temp < 15: 'Зябко',
15 <= temp < 25: 'Тепло',
25 <= temp: 'Жарко'
}[True]
This dictionary after calculation will have two keys True
and False
. We are interested in the True
key. Be careful that the conditions do not overlap !
Dictionaries like this can check for an arbitrary property, such as a type ( example source ):
selector = {
type(x) == str : "it's a str",
type(x) == tuple: "it's a tuple",
type(x) == dict : "it's a dict"
}[1] # можно использовать число 1 как синоним True
If you need more complex actions, store the function as a value for each key:
import operator
operations = {
'+': operator.add,
'*': lambda x, y: x * y,
# ...
}
def calc(operation, a, b):
return operations[operation](a, b)
Be careful when using a dictionary with functions – make sure that you do not call these functions inside the dictionary, but pass by key; otherwise, all functions will be executed each time the dictionary is constructed.
other methods
They are provided for informational purposes rather than actual use.
-
Using functions with wildcard names
Let's create a class in which we will write several view methods:
def process_first(self): ... def process_second(self): ... ...
And one dispatcher method:
def dispatch(self, value): method_name = 'process_' + str(value) method = getattr(self, method_name) return method()
You can then use the
dispatch
method to execute the appropriate function, passing its suffix, such asx.dispatch('first')
. -
Using Special Classes
If you want to use
switch-case
syntax in the most similar style, you can write something like the following code :class switch(object): def __init__(self, value): self.value = value # значение, которое будем искать self.fall = False # для пустых case блоков def __iter__(self): # для использования в цикле for """ Возвращает один раз метод match и завершается """ yield self.match raise StopIteration def match(self, *args): """ Указывает, нужно ли заходить в тестовый вариант """ if self.fall or not args: # пустой список аргументов означает последний блок case # fall означает, что ранее сработало условие и нужно заходить # в каждый case до первого break return True elif self.value in args: self.fall = True return True return False
Used like this:
x = int(input()) for case in switch(x): if case(1): pass if case(2): pass if case(3): print('Число от 1 до 3') break if case(4): print('Число 4') if case(): # default print('Другое число')
-
Using the
and
andor
operators .Pretty unsafe way, see example:
# Условная конструкция Select Case x Case x<0 : y = -1 Case 0<=x<1 : y = 0 Case 1<=x<2 : y = 1 Case 2<=x<3 : y = 2 Case Else : y = 'n/a' End Select # Эквивалентная реализация на Python y = (( x < 0 and 'first segment') or (0 <= x < 1 and 'second segment') or (1 <= x < 2 and 'third segment') or (2 <= x < 3 and 'fourth segment') or 'other segment')
This method uses a short scheme for evaluating the
and
andor
operators, i.e. that boolean expressions are evaluated like this:(
t
evaluates toTrue
,f
evaluates toFalse
):f and x = f t and x = x f or x = x t or x = t
The proposed calculation method will work only if the second argument of the
and
operator will always contain aTrue
-expression, otherwise this block will always be skipped. For instance:y = (( x < 0 and -1) or (0 <= x < 1 and 0) or (1 <= x < 2 and 1) or (2 <= x < 3 and 2) or 'n/a')
Will not work correctly in the case of
0 <= x < 1
, because the expression0 <= x < 1 and 0
is0
, and because of this, control will jump to the next argumentor
, instead of returning that zero as the result of the expression. -
If you declare multiple functions:
import sys class case_selector(Exception): def __init__(self, value): # один обязательный аргумент Exception.__init__(self, value) def switch(variable): raise case_selector(variable) def case(value): exc_сlass, exс_obj, _ = sys.exc_info() if exc_сlass is case_selector and exс_obj.args[0] == value: return exс_class return None
This uses the
sys.exc_info
function, which returns a set of information about the handled exception: class, instance, and stack.The code using these constructs would look like this:
n = int(input()) try: switch(n) except ( case(1), case(2), case(3) ): print "Число от 1 до 3" except case(4): print "Число 4" except: print "Другое число"