Python vs. Lisp - by Peter Norvig

"Aaron S. Hawley" <[email protected]>
Newsgroups gmane.org.ballistichelmet.lambda
Organization University of Vermont
Message-ID <[email protected]>
I know little about either language, but found this piece interesting.
I've copied what I found worthwhile (most of it) below.

Python for Lisp Programmers
http://www.norvig.com/python-lisp.html
by Peter Norvig

This is a brief introduction to Python for Lisp programmers. (Although it
wasn't my intent, Python programers have told me this page has helped them
learn Lisp.) Basically, Python can be seen as a dialect of Lisp with
"traditional" syntax (what Lisp people call "infix" or "m-lisp" syntax).
One message on comp.lang.python said "I never understood why LISP was a
good idea until I started playing with python." Python supports all of
Lisp's essential features except macros, and you don't miss macros all
that much because it does have eval, and operator overloading, and regular
expression parsing, so you can create custom languages that way.

I looked into Python because I was considering translating the Lisp code
for the Russell & Norvig AI textbook into Java. Some instructors and
students wanted Java because

   1. That's the language they're most familiar with from other courses.
   2. They want to have graphical applications.
   3. A minority want applets in browsers.
   4. Some just couldn't get used to Lisp syntax in the limited amount of
      class time they had to devote to it.

However, one attempt at getting Java code up and running was largely
unsuccesful. Then I discovered Python and Jython, and thought this might
be a better way to go than Java.

My conclusion

Python is an excellent language for my intended use. It is easy to use
(interactive with no compile-link-load-run cycle), which is important for
my pedagogical purposes. While Python doesn't satisfy the prerequisite of
being spelled J-A-V-A, Jython is close. Python seems to be easier to read
than Lisp for someone with no experience in either language. The Python
code I developed looks much more like the (independently developed)
pseudo-code in the book than does the Lisp code. This is important,
because some students were complaining that they had a hard time seeing
how the pseudo-code in the book mapped into the online Lisp code (even
though it seemed obvious to Lisp programmers).

The two main drawbacks of Python from my point of view are (1) there is
very little compile-time error analysis and type declaration, even less
than Lisp, and (2) execution time is much slower than Lisp, often by a
factor of 10 (sometimes by 100 and sometimes by 1). Qualitatively, Python
feels about the same speed as interpreted Lisp, but very noticably slower
than compiled Lisp. For this reason I wouldn't recommend Python for
applications that are (or are likely to become over time) compute
intensive. But my purpose is oriented towards pedagogy, not production, so
this is less of an issue.

Introducing Python

Python can be seen as either a practical (better libraries) version of
Scheme, or as a cleaned-up (no $@&%) version of Perl. While Perl's
philosophy is TIMTOWTDI (there's more than one way to do it), Python tries
to provide a minimal subset that people will tend to use in the same way
(maybe TOOWTDI for there's only one way to do it, but of course there's
always more than one way if you try hard). One of Python's controversial
features, using indentation level rather than begin/end or braces, was
driven by this philosophy: since there are no braces, there are no style
wars over where to put the braces. Interestingly, Lisp has exactly the
same philosphy on this point: everyone uses emacs to indent their code, so
they don't argue over the indentation. Take a Lisp program, indent it
properly, and delete the parens, and you end up with something that looks
rather like a Python program.

Python has the philosophy of making sensible compromises that make the
easy things very easy, and don't preclude too many hard things. In my
opinion it does a very good job. The easy things are easy, the harder
things are progressively harder, and you tend not to notice the
inconsistencies. Lisp has the philosophy of making fewer compromises: of
providing a very powerful and totally consistent core. This can make Lisp
harder to learn because you operate at a higher level of abstraction right
from the start and because you need to understand what you're doing,
rather than just relying on what feels or looks nice. But it also means
that in Lisp it is easier to add levels of abstraction and complexity;
Lisp makes the very hard things not too hard.

Here I've taken a blurb from Python.org and created two vesions of it: one
for Python [{in curly braces}].. and one for Lisp [(in parens)]. The bulk
of the blurb, common to both languages [is not surrounded by braces or
parens].

    "{Python}/(Lisp) is an interpreted (and compiled), object-oriented,
high-level programming language with dynamic semantics. Its high-level
built in data structures, combined with dynamic typing and dynamic
binding, make it very attractive for Rapid Application Development, as
well as for use as a scripting or glue language to connect existing
components together. {Python}/(Lisp)'s simple, easy to learn syntax
emphasizes readability and therefore reduces the cost of program
maintenance.  {Python}/(Lisp) supports {modules and} packages, which
encourages program modularity and code reuse. The {Python}/(Lisp)
interpreter and the extensive standard library are available in source or
binary form without charge for all major platforms, and can be freely
distributed. Often, programmers fall in love with {Python}/(Lisp) because
of the increased productivity it provides. Since there is no separate
compilation step, the edit-test-debug cycle is incredibly fast. Debugging
{Python}/(Lisp) programs is easy: a bug or bad input will never cause a
segmentation fault. Instead, when the interpreter discovers an error, it
raises an exception. When the program doesn't catch the exception, the
interpreter prints a stack trace. A source level debugger allows
inspection of local and global variables, evaluation of arbitrary
expressions, setting breakpoints, stepping through the code a line at a
time, and so on. The debugger is written in {Python}/(Lisp) itself,
testifying to {Python}/(Lisp)'s introspective power.  On the other hand,
often the quickest way to debug a program is to add a few print statements
to the source: the fast edit-test-debug cycle makes this simple approach
very effective.

To which I can only add:

    "Although some people have initial resistance to the {indentation as
block structure}/(parentheses), most come to {like}/(deeply appreciate
them).

To learn more about Python, if you are an experienced programmer, I
recommend going to the download page at Python.org and getting the
documentation package, and paying particular attention to the Python
Reference Manual and the Python Library Reference. There are all sorts of
tutorials and published books, but these references are what you really
need.

The following table serves as a Lisp/Python translation guide. ..

.. (see full version at: http://www.norvig.com/python-lisp.html

An important point for many people is the speed of Python and Lisp versus
other languages. Its hard to get benchmark data that is relevent to your
set of applications, but this may be useful:

Speeds are normalized so the g++ compiler for C++ is 1.00, so 2.00 means
twice as slow; 0.01 means 100 times faster. For Lisp, the CMUCL compiler
was used. Background colors are coded according to legend on right. The
last three lines give the mean score, 25% to 75% quartile scores (throwing
out the bottom two and top two scores for each language), and overall
range. Comparing Lisp and Python and throwing out the top and bottom two,
we find Python is 3 to 85 times slower than Lisp -- about the same as
Perl, but much slower than Java or Lisp. Lisp is about twice as fast as
Java.

Legend
100+ x C++
50-100 x C++
10-50 x C++
5-10 x C++
1-5 x C++
0-1 x C++

Test		Lisp 	Java	Python	Perl 	C++
hash access	1.06	3.23	4.01	1.85	1.00
excn handling	0.01	0.90	1.54	1.73	1.00
sum number file	7.54 	2.63	8.34 	2.49 	1.00
reverse lines 	1.61 	1.22	1.38 	1.25 	1.00
matrix mult	3.30 	8.90	278.00 	226.00 	1.00
heapsort 	1.67 	7.00	84.42 	75.67 	1.00
array access 	1.75 	6.83	141.08 	127.25 	1.00
list procs-ng 	0.93 	20.47	20.33 	11.27 	1.00
object instt-n 	1.32 	2.39	49.11 	89.21 	1.00
word count	0.73 	4.61	2.57 	1.64 	1.00

Median Relative Speeds
		Lisp 		Java		Python
Median		1.67 		4.61		20.33
25% to 75% 	0.93 to 1.67	2.63 to 7.00	2.57 to 84.42
Range	0.01 to 7.54 	0.90 to 2.47	1.38 to 278

Median Relative Speeds (cont.)
		Perl 		C++
Median		11.27 		1.00
25% to 75%	1.73 to 89.21	1.00 to 1.00
Range		1.25 to 226 	1.00 to 1.00

Gotchas for Lisp Programmers in Python

Here I list problems for me a Lisp programmer coming to Python:

   1. Lists are not Conses. Python lists are actually like adjustable
arrays in Lisp or Vectors in Java. That means that list access is O(1),
but that the equivalent of both cons and cdr generate O(n) new storage.
You really want to use map or for e in x: rather than car/cdr recursion.
Note that there are multiple empty lists, not just one. This fixes a
common bug in Lisp, where users do (nconc old new) and expect old to be
modified, but it is not modified when old is nil. In Python,
old.extend(new) always works. But it does mean that you have to test
against [] with ==, not is, and it means that if you set a default
argument equal to [] you better not modify the value.
   2. Python is less functional. Partially because lists are not conses,
Python uses more destructive functions than Lisp, and to emphasize that
they are destructive, they tend to return None. You might expect to be
able to do for x in list.reverse(), but Python's reverse is like nreverse
but returns None. You need to do it in several statements, or write your
own reverse function. Besides reverse, this is also true for remove and
sort, among others.
   3. Python classes are more functional. In Lisp (CLOS), when you
redefine a class C, the object that represents C gets modified. Existing
instances and subclasses that refer to C are thus redirected to the new
class. This can sometimes cause problems, but in interactive debugging
this is usually what you want. In Python, when you redefine a class you
get a new class object, but the old instances and subclasses still refer
to the old class. This means that most of the time you have to reload your
subclasses and rebuild your data structures every time you redefine a
class. If you forget, you can get confused.
   4. Python is more dynamic, does less error-checking. In Python you
won't get any warnings for undefined functions or fields, or wrong number
of arguments passed to a function, or most anything else at load time; you
have to wait until run time. The commercial Lisp implementations will flag
many of these as warnings; simpler implementations like clisp do not. The
one place where Python is demonstrably more dangerous is when you do
self.feild = 0 when you meant to type self.field = 0; the former will
dynamically create a new field. The equivalent in Lisp, (setf (feild self)
0) will give you an error. On the other hand, accessing an undefined field
will give you an error in both languages.
   5. Don't forget self. This is more for Java programmers than for Lisp
programmers: within a method, make sure you do self.field, not field.
There is no implicit scope. Most of the time this gives you a run-time
error. It is annoying, but I suppose one learns not to do it after a
while.
   6. Don't forget return. Writing def twice(x): x+x is tempting and
doesn't signal a warning or exception, but you probably meant to have a
return in there. This is particularly irksome because in a lambda you are
prohibited from writing return, but the semantics is to do the return.
   7. Watch out for singleton tuples. A tuple is just an immutable list,
and is formed with parens rather than square braces. () is the empty
tuple, and (1, 2) is a two-element tuple, but (1) is just 1. Use (1,)
instead. Yuck. Damian Morton pointed out to me that it makes sense if you
understand that tuples are printed with parens, but that they are formed
by commas; the parens are just there to disambiguate the grouping. Under
this interpretation, 1, 2 is a two element tuple, and 1, is a one-element
tuple, and the parens are sometimes necessary, depending on where the
tuple appears. For example, 2, + 2, is a legal expression, but it would
probably be clearer to use (2,) + (2,) or (2, 2).
   8. Watch out for certain exceptions. Be careful: dict[key] raises
KeyError when key is missing; Lisp hashtable users expect nil. You need to
catch the exception or test with dict.has_key(key) or key in dict or
dict.get(key).
   9. Python is a Lisp-1. By this I mean that Python has one namespace for
functions and variables, like Scheme, not two like Common Lisp. For
example:

def f(list, len): return list((len, len(list)))      ## bad Python
(define (f list length) (list length (length list))) ;; bad Scheme
(defun f (list length) (list length (length list)))  ;; legal Common Lisp

      This also holds for fields and methods: you can't provide an
abstraction level over a field with a method of the same name:

class C:
    def f(self): return self.f  ## bad Python
    ...

  10. Python Pre-2.1 did not have lexical scopes. In Python before version
2.1 there were only two variable scopes per module: global scope and
function scope. In Python 2.1, released in April 2001, if you do "from
__future__ import nested_scopes", you add a third scope, block nested
scope. In Python 2.2, this is the default behavior. This is what you want.
Now you can create closures over read-only variables. If you do need to
modify a variable that you close over in a function, you have a few
options, all of them a little cumbersome. You can wrap the variable(s) in
1-element lists:

def sum(items):
    total = [0.0]
    def f(x): total[0] = total[0] + x
    map(f, items)
    return total[0]
>>> sum([1.1, 2.2, 3.3])
6.6

      Notice also that you could not use a lambda here, because the lambda
function body must be a single expression, not a statement. The other
option is to use objects instead of functions, but this is overly verbose.
Still, verbosity is in the eye of the beholder. Lisp programmers think
that (lambda (x) (* k x)) is about right, but Smalltalk programmers think
this is way too much, they use [:x | x * k], while Java programmers put up
with a ridiculously verbose inner class expression such as:

new Callable() {
    public Object call(Object x) {
        return x.times(k)
    }
}

  11. Python strings are not quite like Lisp symbols. Python does symbol
lookup by interning strings in the hash tables that exist in modules and
in classes. That is, when you write obj.slot Python looks for the string
"slot" in the hash table for the class of obj, at run time. Python also
interns some strings in user code, for example when you say x = "str". But
it does not intern strings that don't look like variables, as in x = "a
str" (thanks to Brian Spilsbury for pointing this out).
  12. Python does not have macros. Python does have access to the abstract
syntax tree of programs, but this is not for the faint of heart. On the
plus side, the modules are easy to understand, and with five minutes and
five lines of code I was able to get this:

>>> parse("2 + 2")
['eval_input', ['testlist', ['test', ['and_test', ['not_test',
['comparison',
 ['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term',
  ['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term',
['factor',
   ['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]

      This was rather a disapointment to me. The Lisp parse of the
equivalent expression is (+ 2 2). It seems that only a real expert would
want to manipulate Python parse trees, whereas Lisp parse trees are simple
for anyone to use. It is still possible to create something similar to
macros in Python by concatenating strings, but it is not integrated with
the rest of the language, and so in practice is not done. In Lisp, there
are two main purposes for macros: new control structures, and custom
problem-specific languages. The former is just not done in Python. The
later can be done for data with a problem-specific format in Python: below
I define a context-free grammar in Python using a combination of the
builtin syntax for dictionaries and a preprocessing step that parses
strings into data structures. The combination is almost as nice as Lisp
macros. But more complex tasks, such as writing a compiler for a logic
programming language, are easy in Lisp but hard in Python.

Comparing Lisp and Python Programs

.. (see full version at: http://www.norvig.com/python-lisp.html

-- 
PINE 4.55 Mailer - www.washington.edu/pine/
source-included, proprietary, gratis, text-based, console email client
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.