Is there a better way to write nested if statements in python?
Question:
Is there a more pythonic way to do nested if else statements than this one:
def convert_what(numeral_sys_1, numeral_sys_2):
if numeral_sys_1 == numeral_sys_2:
return 0
elif numeral_sys_1 == "Hexadecimal":
if numeral_sys_2 == "Decimal":
return 1
elif numeral_sys_2 == "Binary":
return 2
elif numeral_sys_1 == "Decimal":
if numeral_sys_2 == "Hexadecimal":
return 4
elif numeral_sys_2 == "Binary":
return 6
elif numeral_sys_1 == "Binary":
if numeral_sys_2 == "Hexadecimal":
return 5
elif numeral_sys_2 == "Decimal":
return 3
else:
return 0
This script is a part of a simple converter.
Answers:
Insert all the valid combinations to a dictionary
of tuple
s, and if the combination is not there, return 0:
def convert_what(numeral_sys_1, numeral_sys_2):
numeral_dict = {
("Hexadecimal", "Decimal" ) : 1,
("Hexadecimal", "Binary" ) : 2,
("Decimal", "Hexadecimal") : 4,
("Decimal", "Binary" ) : 6,
("Binary", "Hexadecimal") : 5,
("Binary", "Decimal" ) : 3
}
return numeral_dict.get((numeral_sys_1, numeral_sys_2), 0)
If you are planning to use the function in a loop, it may be a better idea to define the dictionary outside the function, so it wouldn’t be recreated on every call to the function.
An Idea is using a list and get index of result, ie.
def convert_what(numeral_sys_1, numeral_sys_2):
if numeral_sys_1 == numeral_sys_2:
return 0
return ["HexadecimalDecimal", "HexadecimalBinary", "BinaryDecimal", "DecimalHexadecimal", "BinaryHexadecimal", "DecimalBinary" ].index(numeral_sys_1 + numeral_sys_2) + 1
If you are sure there is no other value could have been set to numeral_sys_1 and numeral_sys_2 variables this is the simplest and cleanest solution.
On the other hand, you have to extend the dictionary with its combinations with available values, if you have any other value than “Hexadecimal”, “Decimal” and “Binary”
The logic here is; if variable tuples in dictionary keys are not equal to given variable tuple, .get() method returns “0”. If given variable tuple match any key in dictionary thus return value of the matching key.
def convert_what(numeral_sys_1, numeral_sys_2):
return {
("Hexadecimal", "Decimal") : 1,
("Hexadecimal", "Binary") : 2,
("Binary", "Decimal") : 3,
("Decimal", "Hexadecimal") : 4,
("Binary", "Hexadecimal") : 5,
("Decimal", "Binary") : 6,
}.get((numeral_sys_1, numeral_sys_2), 0)
There is also using generator could be a solution. Looks much smarter, but I think hard codded dictionary would faster than using a generator for this simple requirement.
While @Aryerez and @SencerH.’s answers work, each possible value of numeral_sys_1
has to be repeatedly written for each possible value of numeral_sys_2
when listing the value pairs, making the data structure harder to maintain when the number of possible values increases. You can instead use a nested dict in place of your nested if statements instead:
mapping = {
'Hexadecimal': {'Decimal': 1, 'Binary': 2},
'Binary': {'Decimal': 3, 'Hexadecimal': 5},
'Decimal': {'Hexadecimal': 4, 'Binary': 6}
}
def convert_what(numeral_sys_1, numeral_sys_2):
return mapping.get(numeral_sys_1, {}).get(numeral_sys_2, 0)
Alternatively, you can generate the pairs of values for the mapping with the itertools.permutations
method, the order of which follows that of the input sequence:
mapping = dict(zip(permutations(('Hexadecimal', 'Decimal', 'Binary'), r=2), (1, 2, 4, 6, 3, 5)))
def convert_what(numeral_sys_1, numeral_sys_2):
return mapping.get((numeral_sys_1, numeral_sys_2), 0)
In general I would run with dictionary solution for nested if task. Some particular cases may bring to another approach. Like this one:
def convert_what(numeral_sys_1, numeral_sys_2):
num = ['Hexadecimal','Decimal','Binary']
tbl = [[0,1,2],
[4,0,6],
[5,3,0]]
try:
return tbl[num.index(numeral_sys_1)][num.index(numeral_sys_2)]
except ValueError:
return 0
How about something like:
def convert_what(numeral_sys_1, numeral_sys_2):
src = numeral_sys_1, numeral_sys_2
if src == "Hexadecimal", "Decimal":
return 1
if src == "Hexadecimal", "Binary"
return 2
# You get the gist....
if src == "Decimal", "Binary":
return 6
return 0
In my opinion, this convert_what
function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value of convert_what()
. I suggest something like this:
First step, make one function for every combination:
def hex_dec(inp):
return 1234 # todo implement
# do the same for hex_bin, bin_dec, dec_hex, bin_hex, dec_bin
Second step, put the function objects in a dict. Note that there are no () after the function names, because we want to store the function object and not call it yet:
converter_funcs = {
("Hexadecimal", "Decimal"): hex_dec,
("Hexadecimal", "Binary"): hex_bin,
("Binary", "Decimal"): bin_dec,
("Decimal", "Hexadecimal"): dec_hex,
("Binary", "Hexadecimal"): bin_hex,
("Decimal", "Binary"): dec_bin,
}
Third and last step, implement a convert function. The if statement checks if both systems are the same. Then, we get the right function from our dict and call it:
def convert(input_number, from_sys, to_sys):
if from_sys == to_sys:
return input_number
func = converter_funcs[(from_sys, to_sys)]
return func(input_number)
This is done by switch-case statements in most of other languages. In python, I use a simple function with an expression dictionary.
Code:
def convert_what(numeral_sys_1, numeral_sys_2):
myExpressions = {"Hexadecimal" : {"Decimal" : 1, "Binary" : 2},
"Decimal" : {"Hexadecimal" : 4, "Binary" : 6},
"Binary" : {"Hexadecimal" : 5, "Decimal" : 3}}
return (myExpressions.get(numeral_sys_1, {})).get(numeral_sys_2, 0)
Output:
> convert_what("Hexadecimal", "Decimal")
> 1
> convert_what("Binary", "Binary")
> 0
> convert_what("Invalid", "Hexadecimal")
> 0
An alternative way using nested list. Hope it helps!!
def convert_what(numeral_sys_1, numeral_sys_2):
l1 = [["Hexadecimal","Decimal"],["Hexadecimal","Binary"],
["Decimal","Hexadecimal"],["Decimal","Binary"],
["Binary","Hexadecimal"],["Binary","Decimal"]]
return l1.index([numeral_sys_1, numeral_sys_2]) + 1 if [numeral_sys_1,numeral_sys_2] in l1 else 0
I like to keep code dry:
def convert_what_2(numeral_sys_1, numeral_sys_2):
num_sys = ["Hexadecimal", "Decimal", "Binary"]
r_value = {0: {1: 1, 2: 2},
1: {0: 4, 2: 6},
2: {0: 5, 1: 3} }
try:
value = r_value[num_sys.index(numeral_sys_1)][num_sys.index(numeral_sys_2)]
except KeyError: # Catches when they are equal or undefined
value = 0
return value
Using some techniques that the other answers provide and combine them:
def convert(key1, key2):
keys = ["Hexadecimal", "Decimal", "Binary"]
combinations = {(0, 1): 1, (0, 2): 2, (1, 0): 4, (1, 2): 6, (2, 0): 5, (2, 1): 3} # use keys indexes to map to combinations
try:
return combinations[(keys.index(key1), keys.index(key2))]
except (KeyError, ValueError): # if value is not in list, return as 0
return 0
Although not sure if this approach is faster, but could be done using numpy as well:
conditions = [
("Hexadecimal", "Decimal"), ("Hexadecimal", "Binary"),
("Binary", "Decimal"), ("Decimal", "Hexadecimal"), ("Binary", "Hexadecimal"), ("Decimal", "Binary")]
choices = [1,2,3,4,5,6]
and can be used as :
np.select(conditions, choices, default=0)
As @Sadap said,
In my opinion, this convert_what
function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value of convert_what()
. I suggest something like this:
If you’re implementing base conversion for integers, you’re probably going through a common representation anyway: int
. A separate function for each pair of bases isn’t necessary, and the two bases involved don’t even need to know about each other.
Input
Create a mapping from the name of a number system to its base:
BINARY = "Binary"
DECIMAL = "Decimal"
HEXADECIMAL = "Hexadecimal"
BASES = {
BINARY: 2,
DECIMAL: 10,
HEXADECIMAL: 16,
}
allowing you to read inputs with int(text, BASES[numeral_sys_1])
.
Output
Create a mapping from the name of a number system to a format specifier:
FORMATTERS = {
BINARY: "b",
DECIMAL: "d",
HEXADECIMAL: "x",
}
allowing you to write outputs with format(n, FORMATTERS[numeral_sys_2])
.
Example use
def convert(text, numeral_sys_1, numeral_sys_2):
n = int(text, BASES[numeral_sys_1])
return format(n, FORMATTERS[numeral_sys_2])
Either dict can also be made more general by making the values functions instead, if you need to support a different set of formats than int(x, base)
, or more output bases than built-in integer formatting supports.
Is there a more pythonic way to do nested if else statements than this one:
def convert_what(numeral_sys_1, numeral_sys_2):
if numeral_sys_1 == numeral_sys_2:
return 0
elif numeral_sys_1 == "Hexadecimal":
if numeral_sys_2 == "Decimal":
return 1
elif numeral_sys_2 == "Binary":
return 2
elif numeral_sys_1 == "Decimal":
if numeral_sys_2 == "Hexadecimal":
return 4
elif numeral_sys_2 == "Binary":
return 6
elif numeral_sys_1 == "Binary":
if numeral_sys_2 == "Hexadecimal":
return 5
elif numeral_sys_2 == "Decimal":
return 3
else:
return 0
This script is a part of a simple converter.
Insert all the valid combinations to a dictionary
of tuple
s, and if the combination is not there, return 0:
def convert_what(numeral_sys_1, numeral_sys_2):
numeral_dict = {
("Hexadecimal", "Decimal" ) : 1,
("Hexadecimal", "Binary" ) : 2,
("Decimal", "Hexadecimal") : 4,
("Decimal", "Binary" ) : 6,
("Binary", "Hexadecimal") : 5,
("Binary", "Decimal" ) : 3
}
return numeral_dict.get((numeral_sys_1, numeral_sys_2), 0)
If you are planning to use the function in a loop, it may be a better idea to define the dictionary outside the function, so it wouldn’t be recreated on every call to the function.
An Idea is using a list and get index of result, ie.
def convert_what(numeral_sys_1, numeral_sys_2):
if numeral_sys_1 == numeral_sys_2:
return 0
return ["HexadecimalDecimal", "HexadecimalBinary", "BinaryDecimal", "DecimalHexadecimal", "BinaryHexadecimal", "DecimalBinary" ].index(numeral_sys_1 + numeral_sys_2) + 1
If you are sure there is no other value could have been set to numeral_sys_1 and numeral_sys_2 variables this is the simplest and cleanest solution.
On the other hand, you have to extend the dictionary with its combinations with available values, if you have any other value than “Hexadecimal”, “Decimal” and “Binary”
The logic here is; if variable tuples in dictionary keys are not equal to given variable tuple, .get() method returns “0”. If given variable tuple match any key in dictionary thus return value of the matching key.
def convert_what(numeral_sys_1, numeral_sys_2):
return {
("Hexadecimal", "Decimal") : 1,
("Hexadecimal", "Binary") : 2,
("Binary", "Decimal") : 3,
("Decimal", "Hexadecimal") : 4,
("Binary", "Hexadecimal") : 5,
("Decimal", "Binary") : 6,
}.get((numeral_sys_1, numeral_sys_2), 0)
There is also using generator could be a solution. Looks much smarter, but I think hard codded dictionary would faster than using a generator for this simple requirement.
While @Aryerez and @SencerH.’s answers work, each possible value of numeral_sys_1
has to be repeatedly written for each possible value of numeral_sys_2
when listing the value pairs, making the data structure harder to maintain when the number of possible values increases. You can instead use a nested dict in place of your nested if statements instead:
mapping = {
'Hexadecimal': {'Decimal': 1, 'Binary': 2},
'Binary': {'Decimal': 3, 'Hexadecimal': 5},
'Decimal': {'Hexadecimal': 4, 'Binary': 6}
}
def convert_what(numeral_sys_1, numeral_sys_2):
return mapping.get(numeral_sys_1, {}).get(numeral_sys_2, 0)
Alternatively, you can generate the pairs of values for the mapping with the itertools.permutations
method, the order of which follows that of the input sequence:
mapping = dict(zip(permutations(('Hexadecimal', 'Decimal', 'Binary'), r=2), (1, 2, 4, 6, 3, 5)))
def convert_what(numeral_sys_1, numeral_sys_2):
return mapping.get((numeral_sys_1, numeral_sys_2), 0)
In general I would run with dictionary solution for nested if task. Some particular cases may bring to another approach. Like this one:
def convert_what(numeral_sys_1, numeral_sys_2):
num = ['Hexadecimal','Decimal','Binary']
tbl = [[0,1,2],
[4,0,6],
[5,3,0]]
try:
return tbl[num.index(numeral_sys_1)][num.index(numeral_sys_2)]
except ValueError:
return 0
How about something like:
def convert_what(numeral_sys_1, numeral_sys_2):
src = numeral_sys_1, numeral_sys_2
if src == "Hexadecimal", "Decimal":
return 1
if src == "Hexadecimal", "Binary"
return 2
# You get the gist....
if src == "Decimal", "Binary":
return 6
return 0
In my opinion, this convert_what
function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value of convert_what()
. I suggest something like this:
First step, make one function for every combination:
def hex_dec(inp):
return 1234 # todo implement
# do the same for hex_bin, bin_dec, dec_hex, bin_hex, dec_bin
Second step, put the function objects in a dict. Note that there are no () after the function names, because we want to store the function object and not call it yet:
converter_funcs = {
("Hexadecimal", "Decimal"): hex_dec,
("Hexadecimal", "Binary"): hex_bin,
("Binary", "Decimal"): bin_dec,
("Decimal", "Hexadecimal"): dec_hex,
("Binary", "Hexadecimal"): bin_hex,
("Decimal", "Binary"): dec_bin,
}
Third and last step, implement a convert function. The if statement checks if both systems are the same. Then, we get the right function from our dict and call it:
def convert(input_number, from_sys, to_sys):
if from_sys == to_sys:
return input_number
func = converter_funcs[(from_sys, to_sys)]
return func(input_number)
This is done by switch-case statements in most of other languages. In python, I use a simple function with an expression dictionary.
Code:
def convert_what(numeral_sys_1, numeral_sys_2):
myExpressions = {"Hexadecimal" : {"Decimal" : 1, "Binary" : 2},
"Decimal" : {"Hexadecimal" : 4, "Binary" : 6},
"Binary" : {"Hexadecimal" : 5, "Decimal" : 3}}
return (myExpressions.get(numeral_sys_1, {})).get(numeral_sys_2, 0)
Output:
> convert_what("Hexadecimal", "Decimal")
> 1
> convert_what("Binary", "Binary")
> 0
> convert_what("Invalid", "Hexadecimal")
> 0
An alternative way using nested list. Hope it helps!!
def convert_what(numeral_sys_1, numeral_sys_2):
l1 = [["Hexadecimal","Decimal"],["Hexadecimal","Binary"],
["Decimal","Hexadecimal"],["Decimal","Binary"],
["Binary","Hexadecimal"],["Binary","Decimal"]]
return l1.index([numeral_sys_1, numeral_sys_2]) + 1 if [numeral_sys_1,numeral_sys_2] in l1 else 0
I like to keep code dry:
def convert_what_2(numeral_sys_1, numeral_sys_2):
num_sys = ["Hexadecimal", "Decimal", "Binary"]
r_value = {0: {1: 1, 2: 2},
1: {0: 4, 2: 6},
2: {0: 5, 1: 3} }
try:
value = r_value[num_sys.index(numeral_sys_1)][num_sys.index(numeral_sys_2)]
except KeyError: # Catches when they are equal or undefined
value = 0
return value
Using some techniques that the other answers provide and combine them:
def convert(key1, key2):
keys = ["Hexadecimal", "Decimal", "Binary"]
combinations = {(0, 1): 1, (0, 2): 2, (1, 0): 4, (1, 2): 6, (2, 0): 5, (2, 1): 3} # use keys indexes to map to combinations
try:
return combinations[(keys.index(key1), keys.index(key2))]
except (KeyError, ValueError): # if value is not in list, return as 0
return 0
Although not sure if this approach is faster, but could be done using numpy as well:
conditions = [
("Hexadecimal", "Decimal"), ("Hexadecimal", "Binary"),
("Binary", "Decimal"), ("Decimal", "Hexadecimal"), ("Binary", "Hexadecimal"), ("Decimal", "Binary")]
choices = [1,2,3,4,5,6]
and can be used as :
np.select(conditions, choices, default=0)
As @Sadap said,
In my opinion, this
convert_what
function itself is not very pythonic. I guess the code which calls this code has a bunch of if statements too and does the converting depending on the return value ofconvert_what()
. I suggest something like this:
If you’re implementing base conversion for integers, you’re probably going through a common representation anyway: int
. A separate function for each pair of bases isn’t necessary, and the two bases involved don’t even need to know about each other.
Input
Create a mapping from the name of a number system to its base:
BINARY = "Binary"
DECIMAL = "Decimal"
HEXADECIMAL = "Hexadecimal"
BASES = {
BINARY: 2,
DECIMAL: 10,
HEXADECIMAL: 16,
}
allowing you to read inputs with int(text, BASES[numeral_sys_1])
.
Output
Create a mapping from the name of a number system to a format specifier:
FORMATTERS = {
BINARY: "b",
DECIMAL: "d",
HEXADECIMAL: "x",
}
allowing you to write outputs with format(n, FORMATTERS[numeral_sys_2])
.
Example use
def convert(text, numeral_sys_1, numeral_sys_2):
n = int(text, BASES[numeral_sys_1])
return format(n, FORMATTERS[numeral_sys_2])
Either dict can also be made more general by making the values functions instead, if you need to support a different set of formats than int(x, base)
, or more output bases than built-in integer formatting supports.