The switch-case is an essential statement which is widely adopted in most programming languages . It is used in a situation where we want to map some defined actions depending on a given expression.
In most languages, the switch-case statement looks as shown below:
switch(expression){
case pattern1:
//do this
break;
case pattern2:
//do this
break;
case pattern3:
//do this
break;
default:
//do this
}
The first case block whose pattern
matches the result of the expression
gets executed.
The default
block is executed if the given expression did not match any of the patterns defined by the case blocks.
Until version 3.10, Python didn't have an equivalent to the popular switch-case statements . Python developers had to use other means to simulate the working of the switch-case such as by using the if statement or a dictionary. These methods have their own downsides since there are some cases where the native switch-case fits well.
The following example shows how these may be achieved with if statement:
def switch(lang):
if lang == "English":
print("How are you?")
elif lang == "French":
print("comment allez-vous?")
elif lang == "German":
print("Wie geht es dir?")
elif lang == "Swahili":
print("Habari yako?")
switch("Swahili")
and with dictionaries
switch = {
"English":"How are you?",
"French":"comment allez-vous?",
"German":"Wie geht es dir?",
"Swahili":"Habari yako?"
}
print(switch["German"])
match,
case
and _
keywords
From Python 3.10 onwards, three keywords match
,
case
and _
have been introduced to offer builtin support for feature equivalent to switch-case in other languages. Python does not yet treat them as other keywords and they are referred to as soft keywords. As of now, you can use these keywords as identifiers without raising a syntax error which will happen if you use the "normal" keywords. You can view these keywords using the softkwlist
attribute of the builtin keyword module.
import keyword
print(keyword.softkwlist) #
How they work
The three keywords( match
, case
and _
) are now the standard way to go when you want to implement a similar functionality as switch case in other language, their syntax is as follows:
match term:
case pattern1:
#do this
case pattern2:
#do this
case pattern3:
#do this
case _:
default action
The underscore symbol which is also among the soft keywords is used to define the default case for the match statement, the statements under it's case block will get executed only if all the preceding cases fail to match the term.
In some languages like C++, you can only apply switch statements on integers but in Python you can do it with any data type
Our greetings example can now be written in the most standard way as follows,
def match(lang):
match lang:
case "English":
print("How are you?")
case "French":
print("Comment allez-vous?")
case "German":
print("Wie geht es dir?")
case "Swahili":
print("Habari yako?")
match("French")
Use match-case to print whether a number is odd or even.
num = 8
match num % 2:
case 1:
print("Odd")
case 0:
print("Even")
The following example uses the match case block to map an operator to an expression.
def evaluate(num1, num2, oper):
num1, num2 = int(num1), int(num2)
result = None
match oper:
case "+":
result = num1 + num2
case "-":
result = num1 - num2
case "*":
result = num1 * num2
case "/":
result = num1 / num2
case "%":
result = num1 % num2
case "**":
result = num1 ** num2
case _:
return ("Invalid operator '%s'"%oper)
return f"{num1} {oper} {num2} = {result}"
print(evaluate(100,10,"+"))
print(evaluate(3,5,"*"))
print(evaluate(100,10,"/"))
print(evaluate(5,5, "**"))
print(evaluate(100,10,"@"))
Matching types/classes
The match-case statement can also be used to match the type of the given value, this way, the case statements whose expression evaluates to the type of the value will be executed. The following is a basic example:
value = {1, 2, 3, 4}
match value:
case int():
print(f"{value} is an integer.")
case str():
print(f"{value} is string.")
case set():
print(f"{value} is a set.")
case list():
print(f"{value} is a list.")
In the above example, value
is a set
, therefore, the third case statement's expression matches and gets executed.
Note the parentheses following the name of those types i.e int()
, str()
, set()
and list()
, the parentheses are necessary when matching classes, remember that builtin types are classes. We are actually instantiating an object of that types and then its attributes are matched with that of the given value. Not using the parentheses with class expressions may lead to a SyntaxError
exception being raised.
this raises a syntax error.
value = {1, 2, 3, 4}
match value:
case int:
print(f"{value} is an integer.")
case str:
print(f"{value} is string.")
case set:
print(f"{value} is a set.")
case list:
print(f"{value} is a list.")
If the constructor of the class that the value belong take arguments, we can pass arguments in the case expressions to match specific objects. Consider the following example:
class Point:
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(x = 2, y = 4)
match p:
case Point(x = 0, y = 0):
print(f"First case matched, {p}")
case Point(x = 1, y = 2):
print(f"Second case matched, {p}")
case Point(x = 2, y = 4):
print(f"Third case matched, {p}")
case Point(x = 5, y = 10):
print(f"Fourth case matched, {p}")
case _:
print("NO MATCHES")
Obviously, the above usage assumes that the constructor of the class retains attributes.
Unpacking in case expressions
When the value to be matched is an iterable we can use a syntax similar to unpacking. This allows us to use aliasing and other unpacking features as well as other advanced unique features, consider the following example:
seq = (7, 8)
match seq:
case (1, 2):
print('First case matched!')
print(f"seq = {(1, 2)}")
case (2, b):
print('Second case matched!')
print(f"seq = {(2, b)}")
case (a, 8):
print('Third case matched!')
print(f"seq = {(a, 8)}")
case _:
print('NO MATCHES!')
Note carefully the expressions of the case blocks in the above examples. In the first case block we used all known values i.e (1, 2)
, meaning this block will only match if seq
's values are strictly 1
and 2
. But in the second and third case block's expressions, we used aliasing. In the second case's expression, we passed only the first value and then used alias b
for the second value, this tells the match
statement to assign b
to the value of the second element in seq
. The same is true with the third case statement where we only passed the second value and aliased the first as a
, thus the match statement assigns a
to the value of the first element in seq
.
To understand the above usage better, check the following example:
seq = (7, 8, 3)
match seq:
case (2, b, c):
print('First case matched!')
print(f"seq = {(2, b, c)}")
case (7, b, c):
print('Second case matched!')
print(f"seq = {(7, b, c)}")
case (4, b, c):
print('Third case matched!')
print(f"seq = {(4, b, c)}")
case _:
print('NO MATCHES!')
In the above example, we aliased the second and the third values for all case statements, thus the only defining factor is the first value which were entered manually.
If you find the above usage hard to grasp, you should first familiarize yourself with how unpacking works here. It is also possible to use more advanced unpacking features in the case expressions when you understand unpacking.
case expression with an if
clause
We can embed an if
clause in a case
condition to make the match more strict for that particular case
block. If the pattern matches the case block but the condition specified by if
fails, the match will go on to the next case block.
def evaluate(num1, num2, oper):
num1, num2 = int(num1), int(num2)
result = None
match oper:
case "+":
result = num1 + num2
case "-":
result = num1 - num2
case "*":
result = num1 * num2
case "/" if num2 != 0:
result = num1 / num2
case "%":
result = num1 % num2
case "**":
result = num1 ** num2
case _:
return ("Invalid operator '%s' with operands %s and %s"%(oper, num1, num2))
return f"{num1} {oper} {num2} = {result}"
print(evaluate(100,10,"/"))
print(evaluate(100, 0,"/"))
In the above example we used the if
clause in the case of "/"
operator to make the match go on if num2
is equal to 0 . This ensures division by zero is not carried out hence avoiding the ZeroDivisionError
exception.