Structuring the income and tax-rate data as a table (list of tuples or tuple of tuples) is a huge improvement. As shown in that example it allows one to approach the rest of the task with a table driven approach (traverse up the table to find the top rate for a given salary, then traverse from that point downward accumulating taxes and accounting for the total salary).
To make all of this my "Pythonic" we'd define the functionality as well as the tax rate table above the if __name__==
line. This would implicitly allow us to import our file into any other code and use that functionary.
The part below the if __name__ ==
line is then a driver which calls the functionality with any input given (or can be used to hold unit tests, so that any module can be called on to test it's own functionality).
So our code could look something like:
#!/usr/bin/env python
tax_table = (
(8350, 0.10),
...
)
def compute(salary):
'''Compute taxes for a given salary'''
result = 0
accounted_for = 0
...
return result
if __name__ == "__main__":
import sys
try:
sal = float(raw_input("Please enter salary: ")
except EnvironmentError, err:
print >> sys.stderr, "Error with your input, aborting"
print >> sys.stderr, "The error was:", err
sys.exit(1)
print compute(sal)
Notice that we've now separated reusable functionality from our usage ... which allows us to re-use the code ... but also facilitates test-driven development and refactoring. We can write non-interactive test suites using our same API (so far just a call to the compute() function) and this will allow us to refactor with confidence (and without touching our usage below --- which is our "application" in this case).
It's not clear that this particular code would benefit from being refactored into one or more classes. Certainly the ability to instantiate a class with a different tax table would be handy. Then the tax rate could be stored elsewhere (read from a file, pulled off a web server, or queried out of a database; Python make all of those almost equally easy).
However, we don't have to go "OO" to add that functionality to our compute() function.
We could add an optional parameter to the compute function such that it would use a different tax rate table if we provide one, or default to the one we've hard-coded into the module. For that we simple change the initial function definition line to: def compute(salary, table=tax_table):
... and we fix up some handling for the upper limit (factoring the 0.35 rate out of the function and into the table, with either "sys.maxint" as our limit or the "None" object).
For such a simple exercise it's not worth much worry. But in general it's best to put significant effort into defining our desired APIs up front. If you can come up with a robust, flexible API then any conforming implementation that meets your initial requirements (correctness and acceptable performance) will allow you to deliver your application.
More importantly you can then re-implement at will. Perhaps a really complex tax table needs to be searched using something like the bisect module because the linear searches take too long to find the top tax rate, or some sorts of income tax credits and deductions or number of dependents need to be passed into the compute() function, etc.
Ideally such changes can be done transparently. None of your existing usage should have to change because you've done a re-implemented the internals of our module. Even when you've added functionality you shouldn't need to worry about your existing usage (optional parameters and "key word" arguments (dictionaries passed after optional arguments) let us do that for functions, and classes can add attributes and methods without disturbing any proper existing usage. (Yes, it's possible for subclassing usage to be broken by some changes; but that should not usually be a problem).
In Python one can write something as a simple Python module, later re-implement it as a package or re-impement it as a compiled C module or as a package containing some C modules ... all without affecting the usage. From the user's perspective the import statement works identically on Python modules, packages, and compiled modules ("shared objects" or DLLs).
Historically this has been a huge advantage to Python in its own development. They've been able to add considerable functionality to existing libraries and only rarely been forced through "deprecate/rename" contortions. Quite a bit of the functionality slated for Python 3.0 was able to be added to Python 2.7 for this reason.