Tuesday, 5 May 2015

Learning Python (part 1)

My programming language CV is pretty Java centric since about 1999, with some Haskell along the way, a general background of SQL and shell scripts for most of my career and a selection of languages that are very much occasional or in the past and I'm not going to feed to a search engine!

An impending change of day-job sees me needing to get to grips with Python. It's a language I've been aware of for years, but never had cause to use until now. So, how to get familiar? Instalment one of what ought to be a series below. I'm deliberately exposing the learning process here - I'm not going to make claims that these are brilliant examples of python. It will be interesting to return to these as I develop...

Step 1, is obviously to take a look at the python.org web site and get a general feel. Maybe write "hello world".
Step 2, is to run through a tutorial. It isn't one of the "7 languages" so I looked around and found Google's Python Class and worked through that. It was pretty useful, gave me a sense of the strengths and style of the language, and an overview of key features.
Step 3 will be to have a bash at a few Katas. I'm a firm believer in learning by doing and in the case of programming doing comes out as a mix of thinking and typing. I want to do some more or less unguided problem solving, and find an approach that fits the way I like to (behaviour in unit) test drive my code. Katas give a good chance of finding a selection of other solutions out there to compare with and tend to be a tidy quiet-evening self contained activity.
Step 4 will be to build something bigger, probably with Django. But I'm still firmly at step 3.

First up was the "String Addition" kata, as described by Roy Osherove. My code is below, tests are written in the order I wrote them.

import unittest
from string_calc import add

class Test(unittest.TestCase):

    def testAddNothing(self):
      self.assertEqual(0, add(""), "empty string should return zero")
    def testAddOne(self): 
      self.assertEqual(1, add("1")) 
    def testAddTwo(self):
      self.assertEqual(3, add("1,2"))

    def testAddThree(self):
      self.assertEqual(6, add("1,2,3")) 

    def testAddLongerNumbers(self):
      self.assertEqual(111, add("1,10,100"))
    def testHandleWhitespace(self):
      self.assertEqual(10, add("1\n2, 3 4"), "not handling whitespace")
    def testShouldRejectNegatives(self):
      self.assertRaises(ValueError, add, "1, -2, 1")
if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']

String Calculator Kata
from problem by Roy Osherove
Created on 3 May 2015

@author: Dan Chalmers

import re
from sys import argv

def add(number_string):
  # break the string into a list of number strings
  number_list = re.findall(r'-?\d+', number_string)
  # check for bad input
  if any(number[0] == '-' for number in number_string):
    raise ValueError("")
  return sum(map(int, number_list))
if __name__ == '__main__':

So, what did I learn?

  • Some familiarity with PyDev in eclipse. I spent a little while working out what was there and how I'd want to use it in a bigger project, not just pushing at the path of least resistance.
  • A first go with unittest. This was agreeably straightforward, although I'd cheerfully loose all those "self." references.
  • Reminded myself of the basics of python and found that things were easier by the end.
  • There were a few multiple tests failing runs while I sorted the regular expression and use of re out, but generally each move added something or refactored without breaking the earlier cases.
  • I found myself moving from
    split(<separator>) to
    re.findall('\d+', numbers)
    in order to handle newline and commas together. At that point being able to specify delimiters stopped making much sense as well formatted input is promised. Using a number as part of a delimiter felt bloody minded so had a slight spec out of the pram moment!
  • Gary Bernhardt's solution video reminded me of sum, any and map, so I refactored to use them as I do like that more functional style and wanted to get it in my fingers. Before that it used explicit loops.
Next stop, something maybe a little more involved and with a deliberate "take a functional / no loops" constraint.

No comments:

Post a Comment