First off, great that you have a separate prompt
function. Too often people don't do this and it's much better separated into it's own function. However, you keep it very light on purpose right now. Much better would be to perform input validation here, ensure that the user has provided something you can actually work with. Even at a basic level, you could test for an empty string:
def prompt():
while True:
userInput = input("> ")
if len(userInput) > 0:
return userInput
print("Please enter a command.")
You can see I used a while
loop to keep asking for input until the user enters something valid, and then the function returns whatever their command is. This is only a small amount of validation, but it should give you an idea about how to expand on the idea.
You should have more small functions like prompt
. For example, your number extracting process is the same in each arithmetic function but you copy and paste it anyway. Instead, have one function to handle that in all cases.
def parse_numbers(user_input):
numbers = re.findall(r'\d+', user_input)
nums = ''.join(numbers)
return nums.replace(" ", "")
One thing that's odd about this function is that it only allows for single digit numbers. You should use the actual operator to split up separate numbers and that way you can have multiple digits.
>>> "12+251".split('+')
["12", "251"]
With this, I'd modify parse_numbers
to instead split(+)
:
def parse_numbers(user_input, operator):
numbers = user_input.split(operator)
Note that we nowhave to pass the operator
parameter, this way we can call parse numbers on any arithmetic command, whether it has a +
, -
, *
or \
.
We could simply return numbers
now, but then we'd have no input validation. So instead lets try turning all the string into numbers before we return them. A good way to do this is using map
, which will apply a function to every item in a list. So we can apply int
to each number and then return it. We can also use try except
to make sure that if anything can't be converted to a number we tell the user what happened.
def parse_numbers(user_input, operator):
numbers = user_input.split(operator)
try:
return map(int, numbers)
except ValueError as error:
print (error.message)
except ValueError
means that if an invalid value is passed to int
(like "hello") Python will catch the error. This prevents the whole program from ending and gives you an opportunity to instead inform the user why their input is invalid. That's what print (error.message)
does, as that will tell the user something useful like:
invalid literal for int() with base 10: 'hello'
To continue with all these functions, I think you should have a function for each command. They can be quite short now that you defined parse_numbers
but they still help organisation:
def add(command):
numbers = parse_numbers(command)
return numbers[0] + numbers[1]
Easy, right? And you could do this with all of them. Note that I'm using return
instead of print because it's best to have functions actually return values in case you ever need them for more than just displaying the result. Also, it's not much harder to account for more than two numbers, ie. "23+3+1"
. It's very easy for add
, you can use a built in function called sum
that gets the total sum of all elements in a list.
def add(command):
numbers = parse_numbers(command, '+')
return sum(numbers)
For other functions it's not so easy, but you can use something called reduce
. reduce
basically takes each element of the list and combines them through some function until all the elements have been combined. This does exactly what you need for the other cases. For reduce
you need to pass a list as well as a function, in this case we can use a special function called a lambda
, they're basically small one line functions. They're useful in cases like this where you don't need to bother defining a whole function to do the job. They're quite compact but still look like functions just without names. Here's how subtract
would look:
def subtract(command):
numbers = parse_numbers(command, '-')
return reduce(lambda x, y: x - y, numbers)
The same principle can operate on *
and /
to give you a set of four arithmetic functions. In Python 3 this function was moved to the functools
module, so you'll need add an import
for it.
from functools import reduce
Of course your other two are even simpler, they're just basic print calls.
def quine(command):
print(command)
def print_command(command):
print(command.strip("p "))
Two points on these though. First, I have no idea what a quine is. I had to google it and I still don't see why it's implemented here? You could explain it with a comment, but now that it's a function you could put on a docstring to explain what it does. Docstrings are programmatically accessible comments that explain a function or class. Good practice to put them on your functions. Even if add
is quite clear in its use and function, it's good to note that it operates on a list of any length of integers and returns a single integer.
Second, your print command will accidentally remove any 'p's from the end of a string. strip
actually removes all the characters that it's passed, not just a specific string. So see what happens here:
>>> "p ppp p p p ppppp pp p ".strip("p ")
""
Instead, just using slicing. Slicing allows you to use indices to get a certain range from a string or list. In your case, you just want to get from the 3rd element on. Indexing starts at 0, so the third element is index 2. You can slice command
with [2:]
. The lack of a second parameter after :
means that it will go to the end of the string.
Now, since we have a bunch of handy functions, this will make the parse
function even neater. Here's how it could look:
def prompt():
while True:
userInput = input("> ")
if len(userInput) > 0:
return userInput
print("Please enter a command.")
def parse_numbers(user_input, operator):
numbers = user_input.split(operator)
try:
return map(int, numbers)
except ValueError as error:
print (error.message)
def add(command):
numbers = parse_numbers(command, '+')
return sum(numbers)
def subtract(command):
numbers = parse_numbers(command, '-')
return reduce(lambda x, y: x - y, numbers)
def multiply(command):
numbers = parse_numbers(command, '*')
return reduce(lambda x, y: x * y, numbers)
def divide(command):
numbers = parse_numbers(command, '/')
return reduce(lambda x, y: x / y, numbers)
def parse(command):
if command.startswith("q"):
quine(command)
elif command.startswith("p "):
print_command(command)
elif '+' in command:
print (add(command))
elif '-' in command:
print (subtract(command))
elif '*' in command:
print (multiply(command))
elif '/' in command:
print (divide(command))
else:
print("Invalid command")
You'll note I added a print to inform the user when they've passed a totally invalid command too. I'm also not keen on the long chain of elif
s, you could remove them partially with dictionaries since they're all basically calling a function with the same parameter. The difficulty is figuring out how to extract the command from your string without clashes. This is more of a design than programming problem, because if you're going to extend this you need to find a safe way to define your syntax that's both computer and human readable.
+65
style notation instead of6+5
? \$\endgroup\$ – SuperBiasedMan Oct 7 '15 at 13:46+65
notation was just to make it easier to code. (I don't know how to implement the6+5
style notation...) No, I don't want to remain with the+65
notation. \$\endgroup\$ – m654 Oct 7 '15 at 13:50