views:

459

answers:

7

I've been reading about python for a week now and just thought I'd try my hand at it by creating a tax bracket calculator. I'm not finished but I wanted to know if I'm on the right track or not as far as python programming goes. I've only done a little C++ programming before, and it feels like it shows (good/bad?)

#There are six brackets define by the IRS as of 2009
#Schedule X - Single
first_bracket = 8350
second_bracket = 33950
third_bracket = 82250
fourth_bracket = 171550
fifth_bracket = 372950

def b1(a):
 a = a * .10
 return a
def b2(a):
 a = a * .15
 return a
def b3(a):
 a = a * .25
 return a
def b4(a):
 a = a * .28
 return a
def b5(a):
 a = a * .33
 return a
def b6(a):
 a = a * .35
 return a

if __name__ == '__main__': #importing is fun

 #Ask for salary
 salary = float(raw_input("Enter your salary\n")) 

 #First bracket
 if salary >= 0 and salary <= first_bracket:
  taxed = b1(salary)
  #print "You make less than $", float(first_bracket), "so your tax is $", taxed
  print taxed

 #Second bracket
 elif salary > first_bracket and salary <= second_bracket:
  taxed = b1(first_bracket) + b2(salary-first_bracket)
  #print "You make between $", first_bracket+1, "and $", second_bracket, "so your tax is $", taxed
  print taxed

 #Thrid bracket 
 elif salary > second_bracket and salary <= third_bracket:
  taxed = b1(first_bracket) + b2(second_bracket-first_bracket) + b3(salary-second_bracket)
  print taxed
+13  A: 

There's probably a more efficient way to do this using lists and pairs instead of separate variables for each bracket's limit and rate. For instance, consider the following:

# List of (upper-limit, rate) pairs for brackets.
brackets = [ (8350, .10), (33950, .15), (82250, .25), (171550, .28), (372950, .33) ]

if __name__ == '__main__':

    salary = float(raw_input("Enter your salary\n"))

    accounted_for = 0 # Running total of the portion of salary already taxed
    taxed = 0 # Running total of tax from portion of salary already examined

    for (limit, rate) in brackets:
        if salary < limit:
            taxed += ( (salary - accounted_for) * rate )
            accounted_for = salary
            break # We've found the highest tax bracket we need to bother with
        else:
            taxed += ( (limit - accounted_for) * rate )
            accounted_for = limit

    # If we went over the max defined tax bracket, use the final rate
    if accounted_for < salary:
        taxed += ( (salary - accounted_for) * 0.35 )

    print taxed

The idea is that we don't want to repeat code we don't have to just because we have multiple items of similar data to work with. Tax brackets all function the same, aside from differing rates and limits, so we want those to act as inputs to a standard computation as opposed to their own individual functions.

Amber
nice code !!!!!
Tumbleweed
+1 for moving complexity to data instead of code.
Michael van der Westhuizen
+2  A: 

4 space indenting! Check out this doc, and the

import this
output for more. Looks pretty good to me though, very easy to read.

Pmc
+1: PEP 8 provides most of the style guidance that's required.
S.Lott
@S.Lott, great recommendation. I just wish more people would take the time to read it.
Evan Fosmark
+2  A: 

You can change lines like this:

if salary >= 0 and salary <= first_bracket:

to this:

if 0 <= salary <= first_bracket:

like you'd do in mathematics. It generally makes the code more readable.

mipadi
A: 

Tools like PyLint can catch a lot of errors and bad practices, including bad naming conventions. PyChecker is nice too.

Bastien Léonard
+3  A: 

Particularly when working with financial values, you should consider using the decimal module to make sure that there are no floating-point errors in your output.

Not a big deal when you're just making a toy to learn a language, but good to know for future reference :)

Mark Rushakoff
+1  A: 

Since those functions are fairly simple you could do something like:

def b1(a):
    return a * .10

you could also make one unified taxing function:

def tax_me(salary, rate):
    return salary * rate

Seems like Dav has ya fixed up pretty good :)

Have fun with Python, its a great language.

Casey
+1  A: 

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.

Jim Dennis