On 29Apr2015 08:34, Jim Mooney Py3.4.3winXP <cybervigila...@gmail.com> wrote:
On 28 April 2015 at 22:40, Cameron Simpson <c...@zip.com.au> wrote:
We can pick over your code as well if you like. Should we?
Sure, but let me include the full working program after fixup. Although I
still have to write another program to feed it random problems to work it
out. It's a bit spacey, but I ran it through the pep8 program and it
suggested that. I think two spaces between such tiny functions is overkill,
though.
"""
Takes the name of a binary math operation and two numbers from input,
repeatedly, and displays the results until done
"""
def add(a, b):
return a + b
def subtract(a, b):
return b - a
def minus(a, b):
return a - b
These could all do with docstrings. add() is pretty obvious, but the
distinction between minus() and subtract() could do with elaboration.
Also, I would be inclined to define minus() in terms of subtract(), not because
it is faster but because it establishes an equivalence.
def multiply(a, b):
return a * b
def divide(a, b):
return a / b
operations = {'add': add, '+': add, 'plus': add, 'subtract': subtract,
'subtracted': subtract,
'-': minus, 'minus': minus, 'multiply': multiply, '*':
multiply, 'multiplied': multiply,
'times': multiply, 'divide': divide, '/': divide, 'divided':
divide}
I'd make this much more vertical, like this:
operations = {'add': add,
'+': add,
'plus': add,
'subtract': subtract,
'subtracted': subtract,
Easier to maintain, easier to read, easier to modify, and if you use version
control you will get much more meaningful diff output with changes, which aids
code review (which more or less equates to readability and maintainability).
def test_number(astring):
"""
Input: A string that should represent a valid int or float. Output:
An int or float on success. None on failure.
"""
for make_type in (int, float):
try:
return make_type(astring)
except ValueError:
pass
return None
If this is a test function, it should return True or False.
Other have already remarked on this in other threads: the Pythonic way to do
this is usually to attempt the conversion and let an exception get out:
def numberify(astring):
return float(astring)
Consider the calling code. What are you going to do with it. I see you probe
first, which makes some sense in lexical analysis:
def parse_string(math_string):
"""Input: A math string with a verbal or mathematical operation
and two valid numbers to operate on. Extra numbers and operations
are ignored. Output: A tuple containing a function corresponding
to the operation and the two numbers. Returns None on failure.
"""
operation = None
tokens = math_string.split()
numbers = []
for token in tokens:
if token in operations:
operation = operations[token]
elif test_number(token) != None:
numbers.append(test_number(token))
if len(numbers) > 1:
break
if operation is None or len(numbers) < 2:
return None
else:
return operation, numbers[0], numbers[1]
As a lexical exercise probing (test then act) is pretty common and normal.
However, an alternative way to structure this is the "ask for forgiveness"
approach:
try:
operation = operations[token]
except KeyError:
value = numberify(token)
numbers.append(value)
This will raise ValueError if it is neither an operation nor a number.
The practice of raising an exception permits a mode of programming where both
your code and the caller can broadly write the code as though values are
generally valid, and avoids a lot error catching verbiage that obscures the
core logic.
In that vein, you would also modify the function to raise an exception
(typically ValueError) for other invalid math_strings. The advantage here is
that the caller can use your function like this:
operation, *arguments = parse_string(math_string)
and not perform any special testing for getting None back. Instead, the caller
can plow on as though the math_string was valid as well. An exception can
bubble up to a suitable catch point, such as the main loop which reads strings
from the user.
instructions = '''Enter two numbers and one of the four basid math
operations,
either mathematical or verbal. i.e. 3 + 2, 12 divided by 14, 10 minus 4,
etc.
Enter done to quit.
'''
try:
user_input = input(instructions)
while True:
if user_input == 'done':
break
While most programs will accept a special token to exit, you can also just rely
on seeing end of file. Most systems have a way of indicating this from the
keyboard, often ^Z in Windows and ^D on UNIX terminals. So since input() is
documented as raising EOFError in this circumstance you could add that to your
catch below:
except EOFError:
print("End of input from user.")
Then, if parse_string() raises a ValueError in a Pythonic way you'd change:
result = parse_string(user_input)
if result == None:
print("Not a valid math operation.")
else:
func, num1, num2 = result
print(func(num1, num2))
into:
try:
operation, *arguments = parse_string(user_input)
except ValueError as e:
print("Invalid math operation: %s" % (e,))
else:
print(func(*arguments))
Notice that by returning the numeric parts into a list you can later implement
operations with other numbers of arguments than 2.
user_input = input()
I would try to say that just once by putting it at the top of the loop,
changing this:
user_input = input(instructions)
while True:
into:
prompt = instructions
while True:
user_input = input(prompt)
prompt = ''
thus calling input() from only one place. This also makes it easy to re-issue
the instructions if the user offers invalid input by adding:
prompt = instructions
to the "except" clause for a bad math string.
Finally, I might move main code into a main() function, which I would put at
the _top_ of the program:
def main():
instructions = ......
prompt = instructions
while True:
user_input = input(prompt)
..............
and at the bottom put this boilerplate:
if __name__ == '__main__':
main()
This final boilerplate is common in modules. When a python file is invoked
directly the "module" name is "__main__". When the same python file is imported
as a module, __name__ is the module name. This allows you to use the module as
a main program in one circumstance and as a set of library functions when
imported. The main program might be some primary function with code like yours
or alternatively run various unit tests on the module functions if there is no
natural "main program" to give the module.
Often I have both a legitimate main program and also unit tests. In that
circumstance I usually give the "main program" a "test" mode, which will run
the unit tests.
Cheers,
Cameron Simpson <c...@zip.com.au>
_______________________________________________
Tutor maillist - Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor