3
\$\begingroup\$

This is my first ever Python program. I thought I would make a calculator that performs 5 operations: add, subtract, divide, multiply, power. I am aware Python has its own power function, called "pow", but I have defined my own power function for practice.

I've taken the input as a single string, and then I've tried to separate the numbers from the operator in my for loop, using what little Python I currently know. Perhaps this is the section of my code that could be made cleaner, above all else?

Coming from Java, my first instinct was to use a switch statement to handle each operator case. I quickly realised Python doesn't have switch statements, but I discovered that dictionaries are apparently a good substitute. But maybe there is a better way of handling each operator case?

And obviously, I should have error handling, but let's just ignore that detail for now.

val = str(input("Calculate: "))

# get first number, second number, and operator, from string
foundOp = False # has the operator been found?
a, b = "", "" # first and second number
for char in val:
    # concatenate digits
    if char.isdigit():
        if foundOp:
            b = b + char
        else:
            a = a + char
    # skip spaces
    elif char == " ":
        continue
    # find operator
    elif not char.isdigit():
        foundOp = True
        op = char

# convert a and b into int
a, b = int(a), int(b)

# compute a ^ b
def power(a,b):
    original_a = a
    for i in range(1,b):
        a *= original_a
    return a

calc = {
    "+": a + b,
    "-": a - b,
    "*": a * b,
    "/": a / b,
    "^": power(a,b)
}

print(calc[op])
\$\endgroup\$
  • \$\begingroup\$ Just a side remark: the functionality can be achieved in a one-liner: print(eval(input("Calculate:").replace('^', "**"))). It simply reads the input, converts it to a string of corresponding Python expression, and then evaluates and prints the result. \$\endgroup\$ – GZ0 Sep 11 at 23:50
  • 1
    \$\begingroup\$ @GZ0 "can" is not "should". eval is basically never a good idea. \$\endgroup\$ – Reinderien Sep 11 at 23:55
  • \$\begingroup\$ @Reinderien I never said "should". Using eval is generally not good but in this case it is appropriate IMO. It is useful for handling code-like strings. Newcomers do not need to use it often but it would be better for them to know the existence of the built-in. \$\endgroup\$ – GZ0 Sep 12 at 0:01
  • 2
    \$\begingroup\$ eval of user input is classically awful, and is in no way appropriate. Use ast instead. \$\endgroup\$ – Reinderien Sep 12 at 0:03
  • \$\begingroup\$ @Reinderien ast is only for parsing literals, not evaluating an entire expression. Using eval for user input is not recommended primarily due to potential security issues. This post shows how to make eval safe when needed. A basic expression evaluator is actually one primary use case for the eval function. \$\endgroup\$ – GZ0 Sep 12 at 0:32
3
\$\begingroup\$

Use functions

It's generally a good idea to sort your functionality into functions. Allows you for nice reuse and stuff like that.

string.split()

So how will we parse? Of course, you can parse every single input like you do, or even upgrade it to a tokenizer engine if you got a lot of spare time and effort. There's really 2 ways to go about there here. The simple one is string.split(). The complicated one is regexes, but you probably won't need these unless you're planning big.

Map to functions, not precomputed results

You'll notice I import functions from the operator builtin module. These do the exact same thing as the +, -, /, * and **(power) symbols, but we can use them as functions. This lets us put them in a dict we can create before we ask for user input. It also means we don't have to calculate all the operations, and can stick to only calculating the one we actually are interested in. Do note that this also obsoletes your power function, much as you could have done using ** yourself.

from operator import pow, sub, mul, add, truediv

def calculate():
    operators = {
        "+": add,
        "-": sub,
        "/": truediv,  # floordiv also exists, which is integer division
        "*": mul,
        "^": pow
    }
    val = input("Calculate: ")  # Do note that it's already a string, so you don't need to cast to one.
    for symbol, operator in operators.items():
        if symbol in val:  # Check if the symbol is in the string
            a, b = val.split(symbol)
            return operator(float(a), float(b))  # Cast them to floats - all builtin operators can handle them.

if __name__ == "__main__":
    print(calculate())

Last thing I added was what we call a guard. It makes you able to import this function from another script and use it, without having to run it.

If you want to go for bonus points, you can also switch to regexes for your input parsing.

Bonus info: Iterators

Python does a lot of smart things with iterators. For example, we're iterating over a dictionary here. A dictionary looks like this:

my_dict = {
    "key1": "value1",
    "key2": "value2",
    # etc...
}

It's a hashtable for the keys, and under the hood these are linked to pointers to the values. We can iterate over a dictionary in three ways: Over the keys, over the values, and over key: value pairs.

Keys

for key in my_dict:
    # "key1", "key2"....
# OR:
for key in my_dict.keys():
    # "key1", "key2"....

Values

for value in my_dict.values():
    # "value1", "value2"....

key/value pairs

for key, value in my_dict.items():
    # ("key1", "value1"), ("key2", "value2")....

In case of items(), the iterator gives a tuple of length 2 for every iteration. By assigning that tuple to key, value, we unpack it. This works the same as in the line:

a, b = 1, 2

On the RHS, we construct a 2-length tuple (1, 2), which is passed to the LHS, where it is unpacked to a, b. Since tuples enforce ordering, that's how python decides which value to put where.

See also: Python Docs Dictionary Looping Techniques

\$\endgroup\$
  • 2
    \$\begingroup\$ If I enter 42, it prints None \$\endgroup\$ – AJNeufeld Sep 12 at 0:37
  • \$\begingroup\$ That's not a calculation. Therefore it's not really a valid input. Considering the question ("first python program"), I didn't consider watertight input validation a priority here. \$\endgroup\$ – Gloweye Sep 12 at 5:09
  • \$\begingroup\$ @Gloweye Thanks, I learned a whole lot from that. I have two questions about this: for symbol, operator in operators.items(): You seem to be accessing the values of a dictionary inside a function. In Java, a switch statement would be local to the function, so you'd call the function calculate() and return a value. Are dictionaries that are defined in functions not local variables in Python? Why is this allowed? Also, you seem to be assigning a variable to the name of a function. Again, not a thing in Java, I think. Does this feature of Python have a name? What's going on here? \$\endgroup\$ – HumptyDumpty Sep 12 at 12:14
  • \$\begingroup\$ I'll update the answer for this, it's to long for a comment. \$\endgroup\$ – Gloweye Sep 12 at 12:19
  • \$\begingroup\$ @HumptyDumpty for key, value in d.items(): is a short form of for entry in d.items(): key, value = entry. Its Java equivalent is: for (var entry : d.entrySet()) { var key = entry.getKey(); var value = entry.getValue(); }. In Python each entry is a tuple and can be unpacked in a statement like key, value = entry. \$\endgroup\$ – GZ0 Sep 12 at 13:24

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.