Skip to content

4. Application Exercise – [IMPOTS]

4.1. The problem

We aim to write a program to calculate a taxpayer’s tax. We consider the simplified case of a taxpayer who has only their salary to report:

  • we calculate the number of tax brackets for the employee nbParts=nbEnfants/2 +1 if they are unmarried, nbEnfants/2+2 if they are married, where nbEnfants is the number of children;
  • we calculate his taxable income R=0.72*S, where S is his annual salary;
  • we calculate his family coefficient Q = R/N;
  • we calculate his tax I based on the following data.
12620.0 0 0
13190 0.05 631
15640 0.1 1290.5
24740 0.15 2072.5
31810 0.2 3309.5
39970 0.25 4900
48360 0.3 6898.5
55790 0.35 9316.5
92970 0.4 12106
127860 0.45 16754.5
151250 0.50 23147.5
172040 0.55 30710
195000 0.60 39312
0 0.65 49062

Each row has 3 fields. To calculate tax I, we look for the first row where QF <= field1. For example, if QF = 30000, we will find the row:

24740 0.15 2072.5

Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the relationship QF<=field1 is never satisfied, then the coefficients from the last line are used. Here:

0 0.65 49062

which gives the tax I=0.65*R - 49062*nbParts.

4.2. Version with lists


Program (impots_01)


# -*- coding=utf-8 -*-
 
import math, sys
 
def cutNewLineChar(ligne):
    # delete the end-of-line mark if it exists
    l=len(ligne);
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l]);
 
  # --------------------------------------------------------------------------
def calculImpots(marie,enfants,salaire,limites,coeffR,coeffN):
    # married: yes, no
    # children: number of children
    # salary: annual salary
 
    # number of shares
    marie=marie.lower()
    if(marie=="oui"):
        nbParts=float(enfants)/2+2
    else:
        nbParts=float(enfants)/2+1
    # an additional 1/2 share if at least 3 children
    if enfants>=3:
        nbParts+=0.5
    # taxable income
    revenuImposable=0.72*salaire
    # family quotient
    quotient=revenuImposable/nbParts
    # is set at the end of the limit table to stop the following loop
    limites[len(limites)-1]=quotient
    # tAX CALCULATION
    i=0
    while(quotient>limites[i]):
        i=i+1
    # because we've placed quotient at the end of the limit array, the previous loop
    # cannot exceed the limits of the board
    # now we can calculate the tax
    return math.floor(revenuImposable*coeffR[i]-nbParts*coeffN[i])
 
 
# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTATS="resultats.txt"
limites=[12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0]
coeffR=[0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65]
coeffN=[0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062]
 
# reading data
try:
    data=open(DATA,"r")
except:
    print "Impossible d'ouvrir en lecture le fichier des donnees [DATA]"
    sys.exit()
 
# open results file
try:
    resultats=open(RESULTATS,"w")
except:  
    print "Impossible de creer le fichier des résultats [RESULTATS]"
    sys.exit()
 
# use the current line of the data file
ligne=data.readline()
while(ligne != ''):
    # remove any end-of-line marker
    ligne=cutNewLineChar(ligne)
    # we retrieve the 3 fields married:children:salary which form the line
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    # tax calculation
    impot=calculImpots(marie,enfants,salaire,limites,coeffR,coeffN)
    # enter the result
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    # a new line is read
    ligne=data.readline()
# close files
data.close()
resultats.close()

Results

The data file data.txt:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

The resultats.txt file containing the results:

oui:2:200000:22504.0
non:2:200000:33388.0
oui:3:200000:16400.0
non:3:200000:22504.0
oui:5:50000:0.0
non:0:3000000:1354938.0

4.3. Version with text files

In the previous example, the data needed to calculate the tax was found in three lists. It will now be retrieved from a text file:

1
2
3
12620:13190:15640:24740:31810:39970:48360:55790:92970:127860:151250:172040:195000:0
0:0.05:0.1:0.15:0.2:0.25:0.3:0.35:0.4:0.45:0.5:0.55:0.6:0.65
0:631:1290.5:2072.5:3309.5:4900:6898.5:9316.5:12106:16754.5:23147.5:30710:39312:49062

Program (impots_02)


# -*- coding=utf-8 -*-
 
import math,sys
 
# --------------------------------------------------------------------------
def getTables(IMPOTS):
    # IMPOTS: the name of the file containing data from the limit tables, coeffR, coeffN
    # does the IMPOTS file exist?
    try:
        data=open(IMPOTS,"r")
    except:
        return ("Le fichier IMPOTS n'existe pas",0,0,0)
    # create the 3 lists - assume the lines are syntactically correct
    # -- line 1
    ligne=data.readline()
    if ligne== '':
        return ("La premiere ligne du fichier {0} est absente".format(IMPOTS),0,0,0)
    limites=cutNewLineChar(ligne).split(":")
    for i in range(len(limites)):
        limites[i]=int(limites[i])
    # -- line 2
    ligne=data.readline()
    if ligne== '':
        return ("La deuxieme ligne du fichier {0} est absente".format(IMPOTS),0,0,0)
    coeffR=cutNewLineChar(ligne).split(":")
    for i in range(len(coeffR)):
        coeffR[i]=float(coeffR[i])
    # -- line 3
    ligne=data.readline()
    if ligne== '':
        return ("La troisieme ligne du fichier {0} est absente".format(IMPOTS),0,0,0)
    coeffN=cutNewLineChar(ligne).split(":")
    for i in range(len(coeffN)):
        coeffN[i]=float(coeffN[i])
    # end
    return ("",limites,coeffR,coeffN)
 
# --------------------------------------------------------------------------
def cutNewLineChar(ligne):
    # delete the end-of-line mark if it exists
    l=len(ligne);
    while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
        l-=1
    return(ligne[0:l]);
 
  # --------------------------------------------------------------------------
def calculImpots(marie,enfants,salaire,limites,coeffR,coeffN):
    # married: yes, no
    # children: number of children
    # salary: annual salary
 
    # number of shares
    marie=marie.lower()
    if(marie=="oui"):
        nbParts=float(enfants)/2+2
    else:
        nbParts=float(enfants)/2+1
    # an additional 1/2 share if at least 3 children
    if enfants>=3:
        nbParts+=0.5
    # taxable income
    revenuImposable=0.72*salaire
    # family quotient
    quotient=revenuImposable/nbParts
    # is set at the end of the limit table to stop the following loop
    limites[len(limites)-1]=quotient
    # tAX CALCULATION
    i=0
    while(quotient>limites[i]):
        i=i+1
    # because we've placed quotient at the end of the limit array, the previous loop
    # cannot go beyond the limit table
    # now we can calculate the tax
    return math.floor(revenuImposable*coeffR[i]-nbParts*coeffN[i])
 
 
# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"
 
# the data required to calculate the tax has been placed in the IMPOTS file
# one line per table in the form
# val1:val2:val3,...
(erreur,limites,coeffR,coeffN)=getTables(IMPOTS)
 
# was there a mistake?
if(erreur):
    print "{0}\n".format(erreur)
    sys.exit()
 
# reading data
try:
    data=open(DATA,"r")
except:
    print "Impossible d'ouvrir en lecture le fichier des donnees [DATA]"
    sys.exit()
 
# open results file
try:
    resultats=open(RESULTATS,"w")
except:  
    print "Impossible de creer le fichier des résultats [RESULTATS]"
    sys.exit()
 
# use the current line of the data file
ligne=data.readline()
while(ligne != ''):
    # remove any end-of-line marker
    ligne=cutNewLineChar(ligne)
    # we retrieve the 3 fields married:children:salary which form the line
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    # tax calculation
    impot=calculImpots(marie,enfants,salaire,limites,coeffR,coeffN)
    # enter the result
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    # a new line is read
    ligne=data.readline()
# close files
data.close()
resultats.close()

Results

Same as before.


Program (impots_02b)

The getImpots method above (lines 6–36) returns a tuple (error, limits, coeffR, coeffN), where error is an error message that may be empty. You may want to handle these error cases with exceptions. In that case:

  • in case of an error, the method getImpots throws an exception;
  • otherwise, it returns the tuple (limits, coeffR, coeffN).

The code for the getImpots method then becomes the following:


def getTables(IMPOTS):
    # IMPOTS: the name of the file containing data from the limit tables, coeffR, coeffN
    # does file IMPOTS exist? if not, exception IOError is thrown in this case
    data=open(IMPOTS,"r")
 
    # create the 3 lists - assume the lines are syntactically correct
    # -- line 1
    ligne=data.readline()
    if ligne== '':
        raise RuntimeError ("La premiere ligne du fichier {0} est absente".format(IMPOTS))
    limites=cutNewLineChar(ligne).split(":")
    for i in range(len(limites)):
        limites[i]=int(limites[i])
    # -- line 2
    ligne=data.readline()
    if ligne== '':
        raise RuntimeError ("La deuxieme ligne du fichier {0} est absente".format(IMPOTS))
    coeffR=cutNewLineChar(ligne).split(":")
    for i in range(len(coeffR)):
        coeffR[i]=float(coeffR[i])
    # -- line 3
    ligne=data.readline()
    if ligne== '':
        raise RuntimeError ("La troisieme ligne du fichier {0} est absente".format(IMPOTS))
    coeffN=cutNewLineChar(ligne).split(":")
    for i in range(len(coeffN)):
        coeffN[i]=float(coeffN[i])
    # end
    return (limites,coeffR,coeffN)
  • line 4: we do not check if opening the file fails. If it fails, a IOError exception is raised. We let this exception propagate to the calling program;
  • lines 9–10: We throw an exception to indicate that the expected first line is missing. We use a predefined exception RuntimeError. We can also create our own exception classes. We will do this a little later;
  • lines 16–17 and 23–24: we do the same thing for lines 2 and 3.

The main program code then becomes the following:


# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"
 
# the data required to calculate the tax has been placed in the IMPOTS file
# one line per table in the form
# val1:val2:val3,...
 
erreur=False
try:
    (limites,coeffR,coeffN)=getTables(IMPOTS)
except IOError, message:
    erreur=True
except RuntimeError, message:
    erreur=True
 
# was there a mistake?
if(erreur):
    print "L'erreur suivante s'est produite : {0}\n".format(message)
    sys.exit()
 
# reading data
...
  • lines 12-17: we call the getImpots method and handle the two exceptions it may raise: IOError and RuntimeError. In this handling, we simply note that an error occurred;
  • lines 20–22: we display the error message for the intercepted exception and terminate the program.