Newbie - 1st Program

Bruno Desthuilliers bdesth.nospam at removeme.free.fr
Mon Nov 10 07:58:26 EST 2003


Mark Smith wrote:
> Hi,

Ho

(snip)

> It seems to work - great - but how would you make this more 'elegant' - I'm
> sure it's horrible to an experienced hacker...

Not so bad. Well, for a first program, it's even pretty good. I dont see 
anything that would make me scream. But I'm not sure I do qualify as "an 
experienced hacker" either !-)

Now I'll take Ben Finney's good advices one step further, and show you 
how I'd structure this. You may find the exemple somewhat complex or 
confusing, but it may help you or other (well, I hope it will) learn 
some of Python's features and common idioms, and how to cleanly 
modularize code.

Any question welcome, of course !-)


> # Cross-Connect Calculator
> 
> # (c)2003 Mark A Smith
> # Calculates the number of possible routes for a cross connect
> # given a certain number of ingress & egress ports.
> 
> # Get number of ports from user
> 
> inputPorts = input("Enter number of Input Ports: ")
> outputPorts = input("Enter number of Output Ports: ")

You should not use input(). Try entering anything else than a positive 
integer value, and you'll know why.

Here is a sample data acquisition code :

def int_input(msg = ""):
   while (1):
     try:
       user_input = raw_input(msg)
     except EOFError: # user cancelled with ctrl+d
       print "\n"
       val = None
       break

     try:
       val = int(user_input)
     except ValueError:
       # not an integer
       print "'%s' is not a valid input\n" % user_input
       print "(ctrl+d to cancel)\n"

     else:
       # value is an int
       break

   # return an int, or None if user cancelled
   return val


BTW, if you expect to reuse your code in a different context (like, for 
example, a GUI, or batch processing, etc), you should separate data 
acquisition and display from computing. So you would have a function for 
computing, and functions for data acquisition and display

this may look like this :

def countConnectionRoutes(inputPorts, outputPorts):
   """ countConnectionRoutes()

       Compute and return number of possible connection routes
       for a given input ports count and a given
       output ports count
   """
   # NB : you should definitively rewrite this
   # according to Alex Martelli's observations
   totalPorts = float(inputPorts) + float(outputPorts)
   crossConnects = (totalPorts / 2) * (totalPorts - 1)
   return int(crossConnects)


# one very nice thing about Python is having functions as
# first class citizens. It allows useful things like passing
# functions as arguments to other functions.

def uiCountConnectionRoutes(getDataFun, displayDataFun):
   """ uiCountConnectionRoutes()

       Compute number of possible connection routes
       Ask the number or in and out ports to the user,
       using getDataFun and display result using
       displayDataFun

       in : getDataFun
            a function that takes a message string, and
            return an int or None if user cancelled
       in : displayDataFun
            a functio that take a message string
   """
   inputPorts = getDataFun("Enter number of Input Ports: ")
   if inputPorts is None:
     return

   outputPorts = getDataFun("Enter number of Output Ports: ")
   if outputPorts is None:
     return

   crossConnects = countConnectionRoutes(inputPorts, outputPorts)

   # I split this in 2 line since it wraps in my newsreader !-)
   msg = "You have %d possible connection routes." % crossConnects
   displayDataFun(msg)

# a very simple wrapper
def displayData(msg):
     print "%s\n" % msg


# a main function to use this module as a program
def main(argv):
   # the retun status. It may be useful on some OS
   # usually, 0 means 'ok', 1 means 'program error',
   # and 2 means 'wrong args'
   status = 0

   # another fine thing with Python is the possibility
   # to define nested functions that can access vars in
   # the enclosing function (here, the 'argv' argument)
   def usage(msg = ""):
     write = sys.stderr.write
     if len(msg):
       write("%s\n" % msg)
     write("usage : %s [nb_input_ports nb_output_ports]\n" % argv[0])
     write("if called with no args, values will be asked to the user\n")


   # act according to the args passed to the command line
   # remember that argv[0] is usually the name of the program

   if len(argv) == 3 :
     # called with 2 command line args,
     # may be used for batch processing

     try: # check args validity
       inputPorts = int(argv[1])
       outputPorts = int(argv[2])
     except ValueError:
       usage("incorrect args : %s %s" % (argv[1], argv[2]))
       status = 2

     else: # args ok
       crossConnects = countConnectionRoutes(inputPorts, outputPorts)
       # may be used for batch processing, so we keep output
       # to its minimum
       print crossConnects

   elif len(argv) == 1:
     # no args, so we go for interactive version
     uiCountConnectionRoutes(int_input, displayData)

   else:
     # wrong arg count
     usage("incorrect args count : %d" % (len(argv) - 1))
     status = 2

   return status

# run as a program only if called as a program
# (if this module is imported in another one,
#  __name__ will be set to the real name of the module)

if __name__ == '__main__':
   import sys
   status = main(sys.argv)
   sys.exit(status)


It may seems a bit overcomplicated, but this allow you to reuse code in 
different contexts. You can use the computation function without any 
interactive in/out, or use the 'ui' wrapper function with any functions 
that has the right param list and return values, or simply use it as a 
program, here again with the choice of passing args to the command line 
or using the program interactively.

Note that there are still some points to fix (like user entering 
negative int values - try it, it's somewhat funny -, or correcting the 
countConnectionRoutes() function), but _this_ is your job !-)

HTH
Bruno





More information about the Python-list mailing list