38

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.

3
  • 1
    Without using another data structure, you could move the nested if-else statements into and conditions for the top level if-else statements. It would at least be more readable that way. Sadly, python does not have switch statements. Commented Nov 19, 2019 at 6:21
  • This is the pythonic way. Python intentionally does not support switch statements. See python.org/dev/peps/pep-3103 Commented Nov 27, 2019 at 11:31
  • 1
    Not the question at all, but if you're trying to make things more Pythonic, how about defining constants or an enum for the return values - nicer for a reader than "magic numbers".... Commented Nov 27, 2019 at 14:51

13 Answers 13

33

Insert all the valid combinations to a dictionary of tuples, 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.

Sign up to request clarification or add additional context in comments.

8 Comments

except KeyError:
@RomanPerekhrest I've added it, though in this specific question, the function itself doesn't have other types of errors to produce that would give a different output from his original function.
The parens are redundant inside []. Except for the empty tuple, it's the comma that makes it a tuple, not the parentheses, that's just for order of operations in some cases.
You can just use the dict .get() method with 0 default instead of the try statement.
I agree with @gilch, using "try" block is excessive for this case. fetching value with ".get" method is more elegant.
|
16
+50

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)

Comments

16

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.

9 Comments

My interpretation of the last 'else: return 0 ' is that the arguments don't match and could be something else beside those in the list (ie. your dict keys).
@tocode Yes, you are right. But this method is provide same functionality, too. If any or both arguments given to method, say, not string, even being None type value; .get() method return "0" due to lack of key in dictionary. Isn't it simple?
didnt you just copy Aryerez answer?
@Martin No I did not. You are clearly missing the point. There are many ways to do something but teaching correct way is what I willing to do here. Actually there is much better answer below. Take a look at furkanayd's solution. It's flawless and had to be get bounty.
@SencerH.The only difference was that you used dict's method get() which is fundamentally what original answer's try/except does. You cant deny the fact, that you copied idea and very very slightly(without improving) modified and published
|
2

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)

Comments

2

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

4 Comments

this is a good alternative to the top answer and it's easier to extend into more values as well.
This is pretty similar to a previous answer: stackoverflow.com/a/58985114/1895261. Also, I think the last line should return the empty dict rather than 0 in the case that numeral_sys_1 is not in the outer dict: return (myExpressions.get(numeral_sys_1, {})).get(numeral_sys_2, 0)
@Sepia in the question Module_art gives 0 at the else statement, which means return 0 anything doesn't conform with expressions given and equality situation.
Try running print(convert_what("invalid", "Hexadecimal")) with your code. It will raise an error: "AttributeError: 'int' object has no attribute 'get'". Replacing the first 0 with the empty dict ({}) will make the function correctly return 0 in the case numeral_sys_1 is invalid.
1

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

Comments

1

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 

Comments

1

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

Comments

1

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

1 Comment

Interesting suggestion but this doesnot work when arguments are ("Decimal","Not") , which resulted in ValueError: 'DecimalNot' is not in list
1

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.

Comments

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

Comments

0

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

Comments

-1

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)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.