2
\$\begingroup\$

I'm just learning the ropes to programming, so bear with me here. I wanted a simple command-line program to quickly convert decimal numbers into either binary or hexadecimal. This is what I came up with, but I know it can be improved.

while True:
    while True:
        conv_type = input("Hexadecimal or Binary?: ").lower()
        if "b" not in conv_type and "h" not in conv_type:
            print("Invalid Input")
            continue
        else:
            break

    while True:
        try:
            dec_num = int(input("Decimal Number: "))
        except ValueError:
            print("Invalid Input")
            continue
        break

    if "h" in conv_type:
        print("   ", dec_num, "=", hex(dec_num), "in hexadecimal")

    if "b" in conv_type:
        print("   ", dec_num, "=", bin(dec_num).replace("0b", ""), "in binary")

How would you improve this? I feel a bit weird using three while True loops in the same program, but I couldn't think of a better solution on the fly. Any feedback is greatly appreciated!

\$\endgroup\$
1
\$\begingroup\$

User input validation is annoying, right? My instinct is to ask fewer questions. Does the user really need to ask for hex or binary? For example, we could just print both conversions. And if it's a command-line script, couldn't the decimal number come directly from the command line? Or even a bunch of decimal numbers and our script could convert them all.

But if we retain your current approach, the repetitiveness of the validation suggests the need to move code into functions and to create a single function to handle all input() calls. At a minimum, that function needs a user-prompt and a function (or callable) that will convert the value and raise an error if it's invalid. For example, to validate conversion type, we need to lowercase the input and then make sure it contains a b or h. While we're at it, we could simplify downstream logic by defining constants for conv_type. Subsequent code can use strict equality to drive logic (rather than looser in checks). And in this case, the constant can also serve as a label when printing.

A consolidated approach to getting user input allows you to address another issue: how does the user quit the program? However you decide to handle that, it's handy to be able to do it in one place rather than multiple.

If you strip off the 0b prefix for binary shouldn't you do the same for hex? If so, that simplifies the logic, because we can extract the things that vary into a data structure (converters) and then use that structure to drive the logic. The result is that most of the "algorithm" in the main() function fades away and we end up with very little conditional branching. Instead, it just marches step by step, calling utility functions or accessing data structures directly. Instead, the algorithmic complexity is delegated to smaller and more narrowly focused utility functions that are easier to think about in isolation.

import sys

HEX = 'hexadecimal'
BIN = 'binary'

def main(args):
    # Set up conversion functions and prefixes.
    converters = {
        HEX: (hex, '0x'),
        BIN: (bin, '0b'),
    }

    # Perform conversions.
    while True:
        # Get validated input. Since int() will raise if given
        # a bad input, we get that validation for free.
        conv_type = get_user_input('Hexadecimal or binary', is_conv_type)
        dec_num = get_user_input('Decimal number', int)

        # Convert.
        func, prefix = converters[conv_type]
        converted = func(dec_num).replace(prefix, '')

        # Report.
        print(f'    {converted} = in {conv_type}')

def get_user_input(prompt, validate):
    while True:
        # Get input and provide a way for user to quit easily.
        reply = input(prompt + ': ').lower().strip()
        if not reply:
            sys.exit()

        # Return valid converted input or nag the user.
        try:
            return validate(reply)
        except ValueError as e:
            print(f'Invalid input: {reply}')

def is_conv_type(reply):
    # Takes user input. Returns HEX or BIN if valid.
    reply = reply.lower()
    for conv_type in (HEX, BIN):
        if conv_type[0] in reply:
            return conv_type
    raise ValueError()

if __name__ == '__main__':
    main(sys.argv[1:])
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.