Why Not Python?, Part 3

Now it's time for this new Python user to do the hard work--code the program to fill in the blanks of Sudoku puzzles.

Part 1 of this article introduced the Python programming language from the perspective of an old C hacker and presented a short program to solve the "Coconuts" problem. Part 2 described the Sudoku puzzle and showed how a Sudoku-solving program might be written in Python. The program would contain three steps:

  1. read in the values of the cells specified and draw some elementary conclusions about what the blanks will be

  2. fill in the empty cells; that is, solve the puzzle

  3. print out the answer

Last time, we coded the sections of the algorithm for Step 1 and Step 3:

  • 1.2: read in each value from the input file

  • 1.2.1: if unknown (i.e, a '-' or '.'), leave the cell alone

  • 1.2.2: if known:

    a. set the data structure's digit (the "if known" part) to that known value; and

    b. remove that value from the set of possible digits in the rest of that cell's row, column and 3x3 sub-matrix.

We refer to this code as we refine Step 2 below, which involves filling in the empty cells--solving the puzzle. The first thing that came to my mind was something like this:

  • 2.1: for each cell whose value we don't yet know, if its set_of_possibles has only one member, assign that value to the cell and execute 1.2.2b

  • 2.2: for each cell whose value we don't yet know, for each digit in its set_of_possibles, if this cell is the only one in the row, column or 3x3 sub-matrix that could be that value, assign that value to the cell and execute 1.2.2b

  • 2.3: repeat 2.1 and 2.2 until no assignments are made or all cells are filled in

  • 2.4: if any cell is unknown but its set_of_possibles is empty, we have an inconsistent puzzle; raise a signal

  • 2.5: if all cells are known, declare victory and go to Step 3

  • 2.6: create a "guess"

Creating a guess involves the following steps:

  • a. for each cell, save a copy of its value and its set_of_possibles.

  • b. hypothetically fill in an unknown cell with the first value in its set_of_possibles and execute 1.2.2b

  • c. execute 2.1-2.6 recursively

  • d. if we get an inconsistency, restore the copy from 2.6.1 and then go back to 2.6.2. But, this time use the next value in the set_of_possibles; repeat for all values in the set_of_possibles unless...

  • e. if 2.5 said "go to Step 3", then we're done (assume that the puzzle has only one solution, which we've just found).

To see what I mean by 2.1 and 2.2, consider this picture of the above puzzle, where I've labeled three of the "unknown" cells.

Let's apply step 2.1 to the cells marked "A" and "B" in the figure. Cell A's row already has a 1, 2, 3 and 4. Its column has 3, 8 and 7. So it looks like cell A legitimately could be 5, 6, or 9. So 2.1 doesn't help us with A.

Does 2.1 help us with B? Well, B's row has 4, 5, 7 and 8, while its column has 1, 3, 5, 7 and 9. Its submatrix (the grey area) has 3, 4, 5, 6 and 7. The only possible value for B is 2. So Step 2.1 allows us to set cell B to 2.

Now let's apply step 2.2 to A. Because A could be 5, 6 or 9, let's start with the value 5 and see if any other cell in A's row (that is, row 0) also could have the value 5. Well, none of them can. Row 0, column 5 is in the same (grey) submatrix as row 1, column 3 (=5) so row 0, column 5 cannot also be 5. Row 0 columns 6, 7 and 8 can't be 5, because row 2, column 6 is a 5. So row 0, column 1 is the only cell in its row that could be a 5. Thus, Step 2.2 allows us to set cell A to 5.

Step 2.3 says to go through the cells until either all of the cells are filled in or we've made a whole pass without filling in anything. Why do that? Consider cell C. Before cell B is filled in, we might think that cell C could be either 2 or 6. But once we set cell B to 2, C no longer can be 2. Step 2.1 then allows us to set cell C to 6.

Step 2.4 is for the times when we have to make a guess (see Step 2.6). Sometimes we will guess wrong, and we'll want to get out of that.

Now what's this about guessing? Well, Steps 2.1-2.5 are enough to solve every puzzle I've tried that came from a newspaper. But there are some puzzles, such as the one shown below, that require you to guess at some point. Basically, when Steps 2.1 and 2.2 don't let you fill in anything at all, we have to fill in a cell tentatively--we have to guess--and see where it takes us. Sometimes, a guess leads to an inconsistency, which is what Step 2.4 is for. In this case, we know our guess was wrong and we have to forget it. We also must forget all the cells we filled in, including any new guesses, based on that first wrong guess. And that part about forgetting all the cells that we filled in based on the wrong guess--that's why we need Steps 2.6.1 and 2.6.4.

But one thing at a time. For now, let's simply code Steps 2.1-2.5. As I mentioned above, that's enough to solve all the Sudoku puzzles I've tried from the newspaper.

So let me make a few revisions in the class Cell. I need to add a few access functions, plus a test for whether a certain cell could have a given value. While I'm at it, I should put the Cell class into a separate module. To do that, I move everything in the class Cell clause from the main program into a separate file, cell_class.py.

I'm also going to change the names of the fields in cell_class.py. I want some fields to be modified only by functions that are part of the class. So, although Python doesn't let you actually hide data, I can make it my policy always to use the access routines introduced in Part 2 whenever I want to access those fields. To that end, I took the names of all the data fields and prefixed "XYZ" to them, as a kind of "Stop me before I modify private data again!" tool. Then, in the main part of the program, I can make sure there aren't any XYZ's and know I've been a good boy. Here's cell_class.py:

    1   class Cell:
    2      rows = [ [], [], [], [], [], [], [], [], [] ]    # nine each...
    3      columns = [ [], [], [], [], [], [], [], [], [] ]
    4      submatrices = [ [], [], [], [], [], [], [], [], [] ]
    6      # for step 1.1, do "cells[i]=Cell(i)" for i in range(0,81)
    7      def __init__(self,pos):
    8         # "pos" = position in the puzzle.  Valid values: range (0,81)
    9         global rows, columns, submatrices
   10         if pos not in range(0,81):
   11            raise Illegal_pos_in_Cells_initializer
   12         self.XYZpos = pos
   13         self.XYZvalue = 0
   14         self.XYZset_of_possibles = range(1, 10)  # 1-9 INclusive
   16         # For step 1.2.2b, track which row, col, sub that I belong to.
   17         myrow = int(pos / 9)
   18         mycol = pos % 9
   19         mysub = int(myrow/3) * 3 + int(mycol/3)
   21         self.XYZrow = Cell.rows[myrow]
   22         self.XYZcol = Cell.columns[mycol]
   23         self.XYZsub = Cell.submatrices[mysub]
   25         self.XYZrow.append(self)
   26         self.XYZcol.append(self)
   27         self.XYZsub.append(self)
   29      def known(self):
   30         return (self.XYZvalue != 0)
   32      # setvalue is used for 1.2.2
   33      def setvalue(self, val):
   34         # a couple of sanity checks
   35         if val not in range(1,9+1):
   36            raise val_must_be_between_1_and_9
   37         if val not in self.XYZset_of_possibles:
   38            raise setting_impossible_value
   39         if self.known():
   40            raise setvalue_called_but_already_known
   42         self.XYZvalue = val              # 1.2.2a
   43         self.XYZset_of_possibles = []    # make life easier in step2.6
   45         # Now do 1.2.2b
   46         for other in self.XYZrow + self.XYZcol + self.XYZsub:
   47            if other is self: continue
   48            if other.known(): continue
   49            if val in other.XYZset_of_possibles:
   50               # Though "remove" isn't in the book, it's not deprecated...
   51               other.XYZset_of_possibles.remove(val)
   53      def getvalue(self): return self.XYZvalue
   54      def getrow(self): return self.XYZrow
   55      def getcolumn(self): return self.XYZcol
   56      def getsubmatrix(self): return self.XYZsub
   57      def possibles(self): return self.XYZset_of_possibles[:]
   58      def could_be(self,val):
   59         if self.known(): return (val == self.XYZvalue)
   60         return (val in self.possibles())

I slid a few more changes in, which I describe here. In lines 10-11 and 35-40, the "raise" statements are put on their own lines to make debugging (with pdb) easier. For example, I could set a breakpoint on line 36, and I'd hit that breakpoint only when I got an illegal value. This is much more convenient than having a breakpoint on line 35 and hitting it every time I check for a bad value.

Once a cell's value is decided, there is no point in speculating about what other values it might have. Line 43 makes that explicit. It's not really necessary, but the data structure feels a little cleaner this way.

Instead of an .index() and a del call, I put a single .remove() call in line 51. Learning Python, my reference book of choise, didn't say anything about .remove, but it is mentioned in on-line documentation. I didn't see anything that saying .remove was deprecated.

Now, I'll do some minor surgery on what's left. The first few lines now read:

    1   #!/usr/bin/python
    2   import cell_class
    3   import sys
    4   def doit(argv):
    5      cells = []
    6      for i in range(0,81): cells.insert(i,cell_class.Cell(i))       # 1.1
    8      # Here, any cell's set_of_possibles should be full

Line 1 was added because I got tired of typing python s2.py.... I wanted to tell the Linux kernel to run s2.py as a Python program. Besides adding line 1, though, I also had to set execute permission on s2.py, which I did like this:

   % chmod +x s2.py

After that, I could type:

    % ./s2.py 1109.puz 

instead of explicitly typing python every time.

Line 2 says we want to use cell_class.py. Note, though, that we simply say import cell_class; Python will add .py, so it looks for a file named cell_class.py.

Finally, line 6 was changed from

    for i in range(0,81): cells.insert(i,Cell(i))          # 1.1


    for i in range(0,81): cells.insert(i,cell_class.Cell(i))          # 1.1

I did this because, as the tutorial explains, you have to give the name of the module (cell_class in this case) as part of the name:

>>> import fibo

This does not enter the names of the functions defined in fibo directly in the current symbol table; it only enters the module name fibo there. Using the module name you can access the functions:

>>> fibo.fib(1000)

(from docs.python.org/tut/node8.html)

Initially, I forgot to do this; see Appendix A. I then consulted the tutorial and saw the error of my ways.

Below is the complete s2.py program. It contains the code for Steps 1, 2.1-2.5 and 3 as described in this article series. It solves many, but not all, Sudoku puzzles.

Note that there is no XYZ to be found, so the only accesses to cell's data fields are in fact via the access routines. In other words, I have tamed my inner hacker--for now, anyway. Step 2 code starts after line 30:

    1   #!/usr/bin/python
    2   import cell_class
    3   import sys
    4   def doit(argv):
    5      cells = []
    6      for i in range(0,81): cells.insert(i,cell_class.Cell(i))        # 1.1
    8      # Here, any cell's set_of_possibles should be full
   10      if len(argv) > 1 and argv[1]:
   11         input = open(argv[1], 'r')
   12      else:
   13         input = sys.stdin
   15      all_input_lines = input.readlines()
   16      input.close()
   18      which_cell = 0
   19      for one_input_line in all_input_lines:
   20         for char in one_input_line:
   21            if char in '\t\n ': continue
   22            if char in '-.': 
   23               which_cell = which_cell + 1
   24            elif ord(char) in range (ord('1'), ord('9')+1):
   25               cells[which_cell].setvalue(ord(char)-ord('0'))
   26               which_cell = which_cell + 1
   27            if which_cell > 81: raise too_much_input
   28      pass   # so the debugger can break here
   30      # Step 2 should go here
   31      something_was_set = 1
   32      while something_was_set:
   33         something_was_set = 0
   34         unknown_cells = filter(lambda a: not a.known(), cells)
   36         # 2.3 quit when all cells are known
   37         if len(unknown_cells) == 0: break         ## Done!  All known.
   39         for acell in unknown_cells:
   41            # 2.1 only one member in set_of_possibles?
   42            possible_digits = acell.possibles()
   43            if len(possible_digits) == 1:
   44               acell.setvalue(possible_digits[0])
   45               something_was_set = 1
   46               continue
   48            # 2.4 unknown but no possibilities?
   49            if len(possible_digits) == 0: raise Unknown_with_no_hope
   51           # 2.2 any digit in only one cell in a row, column, or submatrix?
   52            global dig
   53            for dig in possible_digits:
   54             whohas = filter(lambda a: a.could_be(dig), acell.getrow())
   55             if len(whohas) == 1: break # unique in row
   56             whohas = filter(lambda a: a.could_be(dig), acell.getcolumn())
   57             if len(whohas) == 1: break # unique in col
   58             whohas = filter(lambda a: a.could_be(dig), acell.getsubmatrix())
   59             if len(whohas) == 1: break # unique in sub
   60            else: # "acell" has no unique digit (no "break" hit)
   61               continue  
   63            # Unique!  Set that value
   64            acell.setvalue(dig)
   65            something_was_set = 1
   66            pass # end of "for acell in unknown_cells"
   67         pass # end of "while something_was_set"
   69      print len(unknown_cells), "unknown cells"
   71      # Code for step 3
   72      for bor in range(0,81,9):
   73         for i in range(bor,bor+9):
   74            if cells[i].known(): print cells[i].getvalue(),     # usual case
   75            else: print '-',                                    # unknown
   76         print                        # end of this row
   78   # main begins here
   79   if __name__ == '__main__': doit(sys.argv)     

Let me explain some of the less obvious parts. Lines 31-33 set up the main loop for Steps 2.1-2.5, where I try to deduce the value of the unknown cells using, well, Steps 2.1 and 2.2. When I go through the entire list of unknown cells and can't determine any value, it's time to give up rather than spin my wheels. So we try, and each time we set the value of a cell (with setvalue()), we make something_was_set "true"; see lines 44-45 and 64-65.

Line 34 uses both "filter" and "lambda". What I'm trying to do in this line is create a list of cells whose values we don't know. These are the cells that we'll look at in Steps 2.1 and 2.2. Now, as the tutorial says:

"filter(function, sequence)" returns a sequence (of the same type, if possible) consisting of those items from the sequence for which function(item) is true. For example, to compute some primes:

      >>> def f(x): return x%2 != 0 and x%3 != 0
      >>> filter(f, range(2, 25))
      [5, 7, 11, 13, 17, 19, 23]

The example from the tutorial uses "f" for the function. What if I don't have a convenient function "f" lying around, and I'm too lazy to define one? After all, the function--I might call it "cell_is_unknown" or something--is going to be used in only one place. Well, it turns out that Python offers a way to create an anonymous function using this lambda thing. In line 34, lambda a: not a.known() means we have a function that returns "true" whenever its single argument refers to a cell of unknown value. So, the effect of line 34 is to put the unknown cells (from the array "cells") into the list "unknown_cells".

If there are no unknown cells, the puzzle is solved already, so we should declare victory and go print out the answer (Step 2.3, line 37). Otherwise, we look at each unknown cell in the loop (lines 39-66).

Lines 42-46 implement Step 2.1. If a given cell can have only one value, we go ahead and set that value (line 44) and then tel the loop that we did something so it's worth trying again (line 45).

Line 49 is going to be important to Step 2.6. Here, the basic idea is: if any of the cells isn't known, but it can't be anything, then the puzzle--as it is right now--can't be solved, because we can't fill in the value for this cell. This is coded as an exception because, at least before we code Step 2.6, it shouldn't ever happen.

Lines 52-60 check to see if, for any of the possible values this cell could have, nobody else in this cell's row (lines 54-55), column (lines 56-57) or submatrix (lines 58-59) possibly could hold that value. If so, we execute lines 64-65 and set that value.

I didn't realize that I would need to declare "dig" as a global in line 52. It's needed because otherwise the "lambda" in line 54 (or 56 or 58) wouldn't know about "dig", due to the Python naming rules. In Learning Python, Lutz and Ascher refer to this as the "LGB" rule--Local, Global, Built-in, the three naming scopes. Line 53 knows about dig because it's local (to the present function), but the lambdas don't. They are lexically separate from the module or function that creates them.

At line 69, the program tells you how many cells are still unknown. I may remove this later. Line 79 is a way that the program can run with or without the debugger. (A reader provided this tip.)

So, let's apply this program to the puzzle shown in Part 2 of this article. The file 1109.puz is an ASCII representation of that puzzle:

    % cat 1109.puz 
     1 - 2 3 4 - - - -
     - - - 5 6 7 - - -
     - - 7 - - - 5 8 4
     6 3 - 7 - 4 - - -
     4 8 - - - - - 9 7
     - - - 1 - 5 - 4 3
     5 7 8 - - - 9 - -
     - - - 9 7 3 - - -
     - - - - 5 6 4 - 2

When we run s2.py on that file, we get:

    % ./s2.py 1109.puz 
    0 unknown cells
    1 5 2 3 4 8 7 6 9
    8 9 4 5 6 7 3 2 1
    3 6 7 2 1 9 5 8 4
    6 3 1 7 9 4 2 5 8
    4 8 5 6 3 2 1 9 7
    7 2 9 1 8 5 6 4 3
    5 7 8 4 2 1 9 3 6
    2 4 6 9 7 3 8 1 5
    9 1 3 8 5 6 4 7 2

As before, in the spirit of full disclosure, my debugging diary appears in Appendix A below.

Now, does this program solve all possible puzzles? It does solve all of the ones I tried from the newspaper, but it doesn't solve this one:

     - - -  - 7 -  9 4 - 
     - 7 -  - 9 -  - - 5 
     3 - -  - - 5  - 7 - 
     - 8 7  4 - -  1 - - 
     4 6 3  - - -  - - - 
     - - -  - - 7  - 8 - 
     8 - -  7 - -  - - - 
     7 - -  - - -  - 2 8 
     - 5 -  2 6 8  - - - 

According to some of the postings on Sudoku.com, it's the hardest known puzzle having a unique solution. It's certainly too hard for my simple program above.

Appendix A: Debugging Steps 2.1-2.5

As I said before, the working code for steps 2.1-2.5 didn't just happen. My first attempt to run the code looked something like this:

        % python s2_orig.py 1109.puz 
        Traceback (innermost last):
          File "s2_orig.py", line 79, in ?
            doit(sys.argv)     ## COMMENT OUT FOR DEBUGGING
          File "s2_orig.py", line 53, in doit
            whohas = filter(lambda a: cell_could_be(a,dig), acell.getrow())
          File "s2_orig.py", line 53, in <lambda>
            whohas = filter(lambda a: cell_could_be(a,dig), acell.getrow())
        NameError: dig

I had forgotten to declare "dig" as global, so the lambda expression in line 53 (now 54) couldn't find it; the remedy was provided here:

       52            global dig

After fixing that, there was another problem:

        % python s2_orig.py 1109.puz 
        Traceback (innermost last):
          File "s2_orig.py", line 80, in ?
            doit(sys.argv)     ## COMMENT OUT FOR DEBUGGING
          File "s2_orig.py", line 61, in doit
            unknown_cells = unknown_cells + 1
        TypeError: illegal argument type for built-in operation

What was I thinking? unknown_cells is a list! I probably thought I was counting up the number of unknown cells. So, I simply deleted the line, because we're using the length of "unknown_cells"; the length is recomputed each time.

Collin Park works for Network Appliance, where he uses Linux on his desktop and laptop computers. He does data recovery and other Linux-related stuff at home, where he lives with his wife and their two teenage daughters. All use Linux to meet their computing needs.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Use of Self

Anonymous's picture

My first impression of python is that it is very "self"ish.

Sudoku via SK and Python

Ryan Nickell's picture

The author of Liquid Weather recently posted a SuperKaramba theme that is a Sudoku game. In case you're not aware, SK themes are written in Python so it may be worth a look.



ago's picture

Great article, it inspired me to clean my old solver and make it more "pythonic". I followed your setps and I used a Cell class, as you showed in the previous article. That really helped to make my code tidier and more object oriented. Here is my implementation. It adds some reduction algorithms and a brute force solver that allow to efficiently solve all complex puzzles, including the difficult problem mentioned above. But the code is still about 100 lines.


Python is impressive!

mangoo's picture

What can I say, I'll start to learn this language.

I didn't know it's possible to solve something like that in just about 150 lines of code!

Why Python?

Christian's picture

Admitted, I needed more about 700 lines of code in php. But that's including empty lines, lines containing only "}" and quite some for the html- interface.

and best of all, it can solve the "hardest known" example

Sorry, don't want to spoil the party, but I, personally, still don't see the advantage of python. never mind.

Re: Why Python?

Andrew Henshaw's picture

I'll submit a version that I wrote a couple of months ago. It weighs in at 82 lines including blank lines and comments and it will solve the hardest known puzzle. Of course I'm biased, but I think that it provides a fairly elegant implementation. Admittedly, it does use the numarray module, but that is a very standard 3rd-party module and that module's base functionality will probably be included in an upcoming release of Python. Not using that module may have cost an extra 10 lines or so. Of course, the program could have been crunched down into fewer lines, but I wanted a very readable version.

I suppose I'd need to graft an html interface onto it for comparison's sake, but this one does include a file reader and command-line argument parsing. Perhaps we could see your code and compare your 700 lines versus the following 82 lines:

''' Sudoku Solver
    Andrew Henshaw'''

from numarray import array

class ConsistencyError(Exception): pass
class BoardSolved(Exception): pass

class Board:
    def __init__(self, board=None):
        if board is not None:
            self.board = board.copy()
    def InitFromString(self, boardstring):
        s = boardstring.replace('\n', '').replace(' ','').replace('-','0').replace('_','0')
        self.board = array([int(x) for x in s]).resize((9, 9))
    def IsValid(self, r, c, value):
        # row check, column check, 3x3 check
        if (value in self.board[r]) or (value in self.board[:,c]): 
            return False
        r = (r // 3) * 3
        c = (c // 3) * 3
        return value not in self.board[r:r+3, c:c+3].flat
    def GetPossibles(self, r, c):
        possibles = [x for x in range(1, 10) if self.IsValid(r, c, x)]
        if not possibles:
            raise ConsistencyError
        if len(possibles) == 1:
            self.board[r,c] = possibles[0]
            return True            
        return possibles
    def Simplify(self):
        self.potentials = {}
        for r in range(9):
            for c in range(9):
                if not self.board[r, c]:
                    self.solved = False
                    result = self.GetPossibles(r, c)
                    if result is True:
                        return True
                        self.potentials[(r,c)] = result
    def _solve(self, level):
        occupied = len(self.board.flat.nonzero()[0])
        while self.Simplify(): 
        deduced = len(self.board.flat.nonzero()[0]) - occupied
        if deduced:
            print '  '*level, 'deduced %d cells' % deduced
        if 0 not in self.board.flat:
            raise BoardSolved, self.board
        # start testing guesses
        row, col = self.potentials.keys()[0]
        for value in self.potentials[(row, col)]:
            print '  '*level, 'trying %d at (%d, %d)' % (value, row, col, )
            self.board[row, col] = value
            testboard = Board(self.board)
            except ConsistencyError:

    def Solve(self):
        print "Here's the starting point:\n", self.board
        print "\nTrying to solve it now ..."
        except BoardSolved, board:
            print "done.\n\nHere's my solution:\n", board

if __name__ == '__main__':
    import sys
    puzzle = open(sys.argv[1]).read()
    b = Board()

Great contribution, Andrew!

OldAl's picture


I think that your program is brilliant. I learned a lot just recasting it to represent the "board" as a simple list. Nothing against numarray module - it is indispensable in Structural Analysis (which has been my speciality before I retired.)

Andrew, I would like to drop you an email, but you do not leave it on the publication. Would you send me a one or two line email, so I can reply, please?Al

Kind regards,


Christian's picture

> Perhaps we could see your code and compare your 700 lines versus the following 82 lines

I forgot to mention that it includes code to generate random puzzles with a unique solution as well, which I find more complicated than solving.

I *never* try to write a program as short as possible. Easily readable and understandable (by others or even myself, a year later) is my first goal.
(I could maybe remove all linebreaks and get a one-line solution ;-)

And I did not want to say, that php is "better" or something.

And I did not want to say, that my program is more elegant. Probably it is not, partly, because php is not really elegant.

Only that: when python was invented, I did not see the need for yet another programing language. I still don't see it. Also see my other post below.

re: well

Andrew Henshaw's picture

I *never* try to write a program as short as possible. Easily readable and understandable (by others or even myself, a year later) is my first goal.
(I could maybe remove all linebreaks and get a one-line solution ;-)

Sure, and as I pointed out, my code was created for readability also. I was suggesting that we compare readability of my 82-line version to your 700-line version.

And I did not want to say, that php is "better" or something.

And I did not want to say, that my program is more elegant. Probably it is not, partly, because php is not really elegant.

Only that: when python was invented, I did not see the need for yet another programing language. I still don't see it. Also see my other post below.

Then why did you bring up PHP? Python precedes PHP by a number of years and is generally acknowledged to be more powerful and readable, so your use of PHP as an example of not needing "yet another programming language" is bizarre.

But, I'll explain "Why Python?" I've used so many languages over the years that it is hard to keep count, but I've never seen a language as versatile, clean, and as fast to develop in, as Python. It spans the gamut of quick throwaway shell scripts to large multi-programmer applications (of which I've been I've been involved with several). Despite its power, the code remains very clean and consistent. It has proven rather easy for our inexperienced programmers to pick up another programmer's work and get up to speed quickly. Python is not a "write-once" programming language (unlike Perl).

disadvantages of PHP

deets's picture

Well, PHP excels in easy web-development. But that's about _all_ on the up-side.

As a language, it is severely limited. No module system (aka recompilation for everything), memory leaks wherever you look, a "one-size-fits-all"-collection type "array" that doesn't support even the simplest of operations... I could continue endlessly.

That is not to say that there aren't nice applications created in PHP. But Python is a general purpose language that has lots and lots of features like metaclasses, decorators, a vast support of libraries and so on that allow to write even large applications outside the web-paradigm in it.

I can show you lots of python standalone apps (e.g. ZOPE, twisted, GTK or PyQt based such as eric, Boa Constructor and so on). How many PHP-ones exist?


Christian's picture

It was not my intention to hype PHP (or my personal programing skills).

My first litte attempt to write a sudoku solver was a small perl script. (140 lines, including comments etc, never intended to be as short as possible). It could only solve the easier puzzles, maybe comparable with the example in python.

Then I wanted to put it online to show it to a friend. Add a html-interface etc.
I decided to rewrite it in php. I still like perl more than php, but with web-applications, php has some advantages over cgi.

whatever. What i wanted to say is only this: when python arrived, I already knew some C/C++ and perl. (and basic, comal, pascal, applescript, shell-script, postscript and lingo ;-).

There seemed already to be a language for every purpose.
So, I did not understand why I should learn yet another language.
(I didn't like the idea that formatting makes a logical difference, either!)
Later, I learned php as yet another language, but here I've seen at least some advantage for some situations.

Re: Sure

Andrew Henshaw's picture

My first litte attempt to write a sudoku solver was a small perl script. (140 lines, including comments etc, never intended to be as short as possible). It could only solve the easier puzzles, maybe comparable with the example in python.

Then I wanted to put it online to show it to a friend. Add a html-interface etc.
I decided to rewrite it in php. I still like perl more than php, but with web-applications, php has some advantages over cgi.

Well, here's a good example for you. You felt it preferable to rewrite your original code in PHP because it had some advantages over Perl. Some languages have advantages.

Among Python's advantages are power, simplicity, and versatility. Python makes an excellent language for HTML coding and is well-situated for an amazing range of other tasks. I wouldn't have hesitated to use Python to put a web frontend on my 82-line version.


Christian's picture

The exaple is not that good:
I would't hesitate to put a html - interface around a perl script either.

(this is going to be off topic but to explain:
The advantage I see in using php with web-applications, is that I have it all in one place. If I want to duplicate a project (preview and live version, or whatever reason) it is often as easy as duplicating a directory. Or using subversion, I need to export only a single working directory.

With perl-cgi, I'd have some parts in htdocs, others in cgi-bin. duplicating both means changing the links between them as well. You can probably get around it but if that involves changing the webserver-config, then it's not going to run on every webhoster's server; it's also a matter of taste.
I guess python cgis are not different to perl-cgis in that aspect, so had i written my first attempt in python, I still might have decided to rewrite it in php for the web. )

php isn't a language that fits every situation. neither is perl (nor C or applescript). But you say python is? Interesting, if true.
I should give it a try some time, then...

Re: perl

Andrew Henshaw's picture

php isn't a language that fits every situation. neither is perl (nor C or applescript). But you say python is? Interesting, if true.
I should give it a try some time, then...

Certainly not every purpose, but an amazingly broad range of them.

languages for every purpose

deets's picture

You basically made two points I'd like to reply to:

- you don't like the important whitespace

- you can't see why one should use a new language anyway, as the old ones can solve your problems

The first one is made very often - and ususally comes from people who never tried to actually use python. Sorry to say so, but I find it ridiculous to judge a feature without actually trying it. Mind you, I'm not talking about doing a large project here - just a few scripts for the taste of it. I've seen people making rebukes about lots of stuff based on such grounds in all kinds of contexts, and I think we can agree that that is not something we ususally don't consider professional or pragmatic.

The second argument one is also oft heard, and of course not only regarding python. And I consider that a very bad one. Why do you use perl if you know C anyway? Both are languages that are turing-copmplete.

Obviously, perl gave you some advantages over C at least for certain tasks. Well, the same is true for python. And even more so, I would go so far and say that it outshines perl on most occasions where you'd consider using it, and even C.

In the end, it is of course a question of taste, and how do you consider the taste of something you'd not even taken a spoonful of - just by seeing others eat and cheer?


christian's picture

I need only to try to understand the posted code to see this: if I want to see if that function is inside or outside that class, I need to count (invisible) spaces.

With other languages, I can mark a bracket and have my editor find the counterpart.
I don't agree that I would have to become a python-programer before I have the right to like or dislike something that obvious.

Eric Raymond used to think the same way you do.

Anonymous's picture

Then he actually tried Python:



You got the Part1 and Part2

rm's picture

You got the Part1 and Part2 links reversed.

RE: prior part links

heather's picture

Thanks, they've been corrected.

-- Web Editor