Python vs Clojure - Evolving

2009-10-14 15:42:40

This is an interesting time to review programming languages! On the one hand I see a general tendency that companies and individuals are leaving the old (un)safe paths and are embracing 'newer' technologies and on the other we see some of these new-comers now moving into maturity. Python has been with us since the late 1980's and this articles take a peek at how Python has handled stepping into the new millennium.

Pythonic overview

Python boasts of being a multi-paradigm  programming language, suitable for Object Oriented Programming (OOP), Functional programming, Imperative programming and even concurrency. When you run your python code it's actually compiled to intermediate bytecode which is then interpreted by the VM. That's similar to Clojure code, compiling to Java Bytecode and being run by the JVM.

Python has a unique syntax and comes in a few different flavors. Finally it's made by Guido Van Rossum who still oversees development.

A word on Syntax

When you are reviewing Python code you'll notice that it all looks very much the same. That's because Python enforces certain amounts of whitespace, 4 units when indenting. This comes with certain benefits, uniform code, no braces/delimiters needed, etc. But on the downside, you don't get to decide what your code looks like, Guido does. This isn't a case of right/wrong, everything is a trade-off but personally I want to be the one who decides what my code looks like.

Not directly related to Syntax comes the issue of scope, which is understood by your level of indenting:

x = 5
if (x == 5):
    y = 10
    print x  # print 5
print y # print 10

The last print statement outputs "10" which is surprising to me, since y has never been declared in that given scope. I don't quite know how Pythonists handle this, but I can imagine many situations where this will be the cause of much confusion. To resolve it you have to manually call 'del y', which is a type of manual garbage collection. I don't have the numbers for Python, but I can't count the number of C app's that have suffered from memory-leaks and what not, based on the lack of automated garbage collection. With Pythons use of scope, I imagine quite a few bugs will follow.

Functional Python

Someone once remarked, that a truly functional programming language is only good for heating your computer. You need at least to generate some output, which can be considered a side-effect. The majority of the Python code that I've seen, has been object oriented and imperative in structure, but there has been written a lot of material on Pythons functional side, some of which I'll look at here. Clojure isn't what you would call a purely functional language, but it comes so close that you really have to concentrate to start defying the main principles. Python, wanting to cater to both sides, doesn't provide the same level of cover, although it does sport immutable collections internally this is of little value when the user representation is still subject to change. To give an example of what I mean, I'll first show a Clojure example and then the corresponding Python code:

(let [x {:foo "bar" :baz 5}]
   (assoc x :quux 10)   ; this returns {:foo "bar", :baz 5, :quux 10}
   x)
{:foo "bar", :baz 5}
    === PYTHON ===

>>> x = {"foo":"bar", "baz":5}
>>> x["quux"] = 10
>>> x
{'quux': 10, 'foo': 'bar', 'baz': 5}

These 2 programs do basically the same thing. In the Clojure version I let 'x' be a hash-map and then associate into that the key/val pair :quux 10. Same thing goes on in Python, just using a 'dict' instead of a hash-map. The only noticeable difference in the definitions is the syntax of course, where Python does not support keyword. Clojure doesn't care about whitespace and regards commas as being such.

Then in the last line of both programs we evaluate 'x' to see which value it holds. The key difference is of course, that Clojures 'x' didn't change while Pythons did. Now in one Python doc I found they actually refer to this type of data as immutable, because if I alias x as y and change x, y remains the same. But this is very different from Clojures concept where immutable means: 'x' never changes. I can make decisions based on my perception of 'x' since it won't change. As we will see later, this makes all the difference in concurrent programming.

Lambda

Python supports anonymous functions called lambdas, these are limited to being 1-liners. For people like CGrand that might not be a problem, but for me it limits functionality. If you combine Pythons First-Class approach to functions with lambdas, you get to do some nice functional programming. Firstly, lets try to implement Pythons counter to Clojure's predicate even?

Clojure:

>> (filter even? (range 10))
( 0 2 4 6 8 )

Python:

>>> even? = 2
  File "", line 1
    even? = 2
        ^
SyntaxError: invalid syntax

Ok, so that was a no go and the reason is I named my Python predicate as I would name a Clojure predicate "even?". Python doesn't support non-alphanumerical characters in definition names, so I'll have to switch to a Common Lisp inspired style instead. To Pythons credit it actually pinpointed the exact character which caused the exception - You shouldn't except that much help from Clojures backtraces....yet.

>>> evenp = (lambda n: (n % 2) == 0)
>>> filter(evenp,range(10))
[0, 2, 4, 6, 8]

Selecting the name evenp lets me save the anonymous function as a predicate. Using the built-in 'filter' I can then interact with a range of numbers just like in Clojure. On both accounts this is purely functional.

Taking it out for a spin

Let's take a look at the Great Funtional Stomping Ground: Project Euler. I think the first 40 or so problems, have been solved using a functional style of Python, so lets compare notes on a couple of them. The Python code is taken from: here.

Euler 1: Find all numbers dividable by 3 or 5 in 0 < x < 1000:

(reduce +
       (filter #(or (zero? (rem % 3))
                    (zero? (rem % 5)))
            (range 1000)))
multiple = lambda x, y: x % y == 0

print sum(x for x in xrange(1, 1000+1)
         if multiple(x, 3) or multiple(x, 5))

On the left we have Clojure, which reads: Take the sum (reduce using addition), of filtering on my predicate from the list (range 1000). My predicate being a normal 'or' statement asking if the modulus value % (our argument) of 3 and 5 respectively is zero.

On the right we have Python who uses a lambda to test for candidates, then uses list comprehension to go through a for loop and pass the result to sum(). I wonder why they didn't use filter. For such a simple task, both languages are almost identical. They don't impose, they don't complicate and they don't add ceremony.

Euler 2: Get the sum of all even Fibonaccis below 4 mill.

So we have the same elements as before

Compared to E1 the only new thing here is getting the Fibonnaci numbers. The Python crew resolved to generating a stream of these fibonnaci numbers, so I'll do the same.

(def fib-seq (lazy-cat [0 1]
     (map + fib-seq (rest fib-seq))))

(reduce +
      (filter even?
            (take-while #(< % 4000000)
                 fib-seq)))
from itertools import takewhile, count

def ireduce(func, iterable, init=None):
    """Like reduce() but using iterators (also
    known also scanl). Non-functional version"""
    if init is None:
        iterable = iter(iterable)
        curr = iterable.next()
    else:
        curr = init
        yield init
    for x in iterable:
        curr = func(curr, x)
        yield curr

def fibonacci():
    get_next = lambda (a, b), _: (b, a+b)
    return (b for a, b in ireduce(get_next, count(), (0, 1)))

candidates = takewhile(lambda n: n < 4000000, fibonacci())
print sum(x for x in candidates if x % 2)

On the left side we read the Clojure code like this: Take the sum of filtering the even numbers, from taking Fibonacci numbers until the item is >= 4 million. I'll admit the fib-seq definition is not intuitively understood for non functional programmers, but that just makes for a good challenge :)

On the right hand side, Python is showing some teeth. Basically what they're doing is defining an ireduce function, which iterates a collection accumulating the result of applying the first arg to every item (the result is similar to using Clojures reduce). Using this function they then define the fibbonacci sequence using a very cryptic statement - and from that point, it's comparable to Clojure.

Alright, last one.

Euler 4: Finding Palindroms

Almost the same components as the above. Instead of getting the sum we get the max, but we're still filtering on a predicate from some list. So we should expect to see almost similar programs to those above, right...?

(reduce max
    (filter #(let [s (str %)]
               (= (seq s) (reverse s)))
            (for [x (range 100 1000)
                  y (range 100 1000)]
              (* x y))))
from itertools import ifilter
import operator

def mul(nums):
    return reduce(operator.mul, nums)

def icross(*sequences):
    if sequences:
        for x in sequences[0]:
            for y in icross(*sequences[1:]):
                yield (x,)+y
    else: yield ()

def digits_from_num(num, base=10):
    def recursive(num, base):
        if num < base:
            return [num]
        return [num%base] + recursive(num/base, base)
    return list(reversed(recursive(num, base)))

def is_palindrome(num, base=10):
    digitslst = digits_from_num(num, base)
    return (digitslst == list(reversed(digitslst)))

def euler4(lstlst):
    canditates = (mul(ns) for ns in icross(*lstlst))
    return max(ifilter(is_palindrome, canditates))

print euler4(2*[range(111, 1000)])

Clojure version reads like this: Take the max from the result of filtering all items where the string representation is equal the the reverse string representation from the sequence which is formed by multiplying all ints i [100-1000] with [100-1000]. When you read and write these stream functions fluently, Euler suddenly becomes a lot easier, as you can see several problems conform to that method. Notice how Clojures lambdas blend into the rest of the code, not being restricted to one-liners.

On the right hand side we have Python. The Python team opted to reverse sequences of ints instead of translating to a string representation, this is handled in digits_from_num, the result is later passed to is_palindrome which basically does the same as my predicate. There are 2 thing which seem to be common for most of the Python attempts and thats nested 'for loops' and reliance on the yield statement. This helps them get to an amazing 29 lines for this simple problem and also makes room for quite a bit of error, although to their credit there is none.

And although it can be a bit premature to speak about effenciency, their very verbose solution runs at almost 15 seconds, which is about 300% slower than the Clojure solution.

Concurrency

It is common knowledge we've hit the Gigahertz wall! When the hardware vendors realized that they couldn't squeak more Ghz out of your processor they decided to put more processors in your system. Our programming languages now need to enable us hammer away at multiple cores, sharing data and dividing labor across many processes, basically: Concurrency. As I've demonstrated in times past, Clojure does this using the STM, Refs, Atoms and Agents. Python doesn't have an STM but it has thread-support and does asynchronous development as well. But before we move on, we need to clarify the term concurrency even further because, like immutability, it's actually not understood the same way by Python programmers as it is by Clojurians.

Concurrency defined

One way to illustrate a property of concurrency is by the allowed activity of worker-threads (T) in your system. It's desirable to a relatively high count of worker-threads working simultaneously to allow for better distribution of labor. That of course comes with quite a few challenges in terms of controlling access to shared data. Here's an illustration

p-vs-c

For Clojure this means, that if you are computing a problem in parallel on 2 cores you get 2x performance minus a scheduling penalty, ditto for 3+ cores.

For Python this means, that no matter how cleverly you distribute your labor Python will enforce sequential execution, giving you no added performance + a 'scheduling' penalty.....?

Meet Gil. Gil is Pythons "Global Interpreter Lock". Basically all threads in Python work under a contract: He who holds the lock, gets to work. And in case you're wondering, this isn't  joke nor a bug in Python per say. Some years ago the core developers realized that this wouldn't fly as core-counts increased, so they removed the GIL and (as I recall) introduced some type of fine grain locking, but the challenge proved too difficult, so this was the result:

[we tried this] once before (in the late ’90s) and the resulting interpreter ran twice as slow

This isn't good considering we're coming up on 2010 and the amount of cores continues to rise. But ok, how much are we paying? David Beazly did a thorough decompilation of the Python VMs handling of concurrent requests and I'll share a few of his findings here.

An example

He made a small countdown function and ran it twice

def count(n):
    while n > 0:
        n -= 1

count(100000000)
count(100000000)

That takes about 30 seconds and thats a 100% serial execution, meaning you count down once, when done you count down again. So what would we expect if we multi thread this operation, so we have both count downs starting in direct succession of one another? 16-17 seconds perhaps? Lets try

import threading

t1 = threading.Thread(target=count,args=(100000000,))
t1.start()
t2 = threading.Thread(target=count,args=(100000000,))
t2.start()
t1.join(); t2.join()

That runs at 43 seconds! That means if you distribute labor in Python, you get a scheduling penalty of 143%.

For comparison I'll show you how to do a comparable benchmark in Clojure:

(defn countdown [n] (when (pos? n) (recur (dec n))))

(time (dotimes [i 2] (countdown 100000000)))
"Elapsed time: 20228.413955 msecs"

(time (doall (pmap countdown (repeat 2 100000000))))
"Elapsed time: 10656.830477 msecs"

The definition of countdown is self-explanatory I hope. First I called the countdown twice using (dotimes [i 2] ...), this is sequential and takes about 20.2 seconds. Then I call it in a pmap (parallel-map) using 'doall'  to force the evaluation, utilizing both of my cores. The results are as you would expect: Much faster than python when run sequentially, much much faster when run in parallel.

Based on my explanation thus far you'd think Python has almost the same speed in parallel as when run sequentially. The last percentages of performance that go missing are based on the way the GIL is implemented. If you want to dig deeper into that, check out the link to Dave Beazlys presentation above.

So Python isn't geared for concurrent programming, it doesn't support you in any meaningful way, so the question is this: Will that change anytime soon? I'll quote Guido:

…you just have to undo the brainwashing you got from Windows and Java proponents who seem to consider threads as the only way to approach concurrent activities.

Just because Java was once aimed at a set-top box OS that didn’t support multiple address spaces, and just because process creation in Windows used to be slow as a dog, doesn’t mean that multiple processes (with judicious use of IPC) aren’t a much better approach to writing apps for multi-CPU boxes than threads.

Just Say No to the combined evils of locking, deadlocks, lock granularity, livelocks, nondeterminism and race conditions.

So what's he saying? He's basically saying that instead of providing solutions to the challenges inherent to concurrent programming, he would rather that you chop up your program into several instances who then communicate using some type of IPC (inter-process-communication method). Leaving Pythonists with IPC is basically saying: "You deal with it, I'm not going to", because this can be anything from from pipes, sockets, you name it. To me the deciding blow is not that Python is unable to meet the requirements of 2009, but rather that its founder is unwilling to improve on this position.

A man with a thousand knives...

...of which none are sharp. It's a chinese proverb which is typically used about martial artists who hold 5+ black belts, but can also be used to describe Python. It seems that in their attempt to support multiple paradigms, they've over complicated things.

In an earlier unrelated blogpost I commented on Pythons poor multi-threading capabilities and aligned with its imperative nature. Some commentators found that to be unreasonable but it's not quite so. The definition of an imperative language goes something like this

In computer science, imperative programming is a programming paradigm that describes computation in terms of statements that change a program state.

When all your data-containers (call them variables for instance) are subject to change (mutable) how can you guarantee a consistent view of the world? If you have 10 threads working on the same data and that data is constantly being altered nobody can make good decisions looking at that data, because the grounds for decision making are all volatile. So you need some way to control this change or to control the workers who can make the change. Pythons GIL is an example of how (not) to handle it, by freezing all workers except one. Try starting the above multi-threaded Python example and hit Ctrl-C, you'll see that Python doesn't even react because the 2 threads are now battling for the attention of the Gil which is constantly context-switching and delays the processing of events.

Easy Access

So since Python has gained so much traction lately, what's the great attraction? What's drawing in the crowds? My best guess would be

  1. libraries
  2. no learning curve
  3. huge community

There are library for everything (hence the slogan 'batteries included'), it reads almost like english and you can google your way out of anything. These are by no means bad things and only on point #1 can Clojure compete. The thing you should notice however, is that all of these are related to 'ease' and not 'quality'.

Conclusions

Generally I think nobody would argue that Python has a very clear syntax, but I truly dislike the fact that Guido gets to decide how I manage my white-space - I'm not free to arrange/align my data the way I want to. This is a style/taste point and I've known skilled competent programmers who prefer this type of rigid syntax. And rightfully so. In a workplace situation, it might not be Guido but a manager who needs to step in and declare some conventions.

I think as an introductory language I might expose a young student to Python for a brief period, then move on to more well-grounded languages before too many bad habits are picked up. Guido is not preparing to transition into modern programming, rather he says that it is to be avoided. With that in mind, Python isn't likely to let you have some fun with all coming challenges within concurrent programming and in my oppinion that disqualifies it for a lot of the serious programming challenges we'll be facing soon and are now facing. It also means that when you've coded your prototype and find out it's performance heavy, your next step is to buy another server chop the input in half and feed each it's portion. I don't dare estimate how many resources are being wasted on this already.

Having mutability be the default, you're teaching yourself a treacherous way of programming, which I guarantee will lead you into many bugs along the way. Again consider how the attraction is 'ease'. It's not difficult to go from only using immutable values to using mutable ones, but it's quite another story to go the other way. I personally don't mind a steep learning curve as long as the payoffs are worth it. Consider it an investment.

The thing that drew hard on me when I was taking on my first Lisp was this question:

"Are you smarter than your programming language?"

If you are, you'll know it because as soon as you can imagine some outline of your programs behavior you're hammering away at the keyboard and 25 - 50 lines later you see that behavior being implemented. With Lisp/Clojure you can sit and ponder your next expression for quite some time, but once you write it down it'll pack a ton of functionality. To me at least, Python falls in the first category and that comes with a huge price-tag: Your horizon isn't being expanded as it should. I read somewhere a staggering statistic, that the seniority and experience of developers wasn't directly related to the quality of their code! How can this be? Well consider a C programmer who has spent 10 years manipulation pointers. What insights has he gained into architectural challenges, concurrency pitfalls, etc? Very little, he spends his days speaking baby-language (low-level) to the compiler in order to get the behavior he wants, at the cost of personal development.

And don't get me wrong, I'm not saying this to put anybody down but rather to draw some attention to the fact that while we're sharpening our skills within one specific language, there's also a personal/mental side that's needs developing. And to do that we need to be challenged in more ways than how to find functions in libraries. For me, Clojure is a big part of that but of course your mileage will vary.

Hope you enjoyed the read,

/Lau

Note: This post has a sequel: here

Update: In version 1.0 of this post (which is now a few hours old) there was an incorrect assumption about Pythons whitespace policy, which has since been corrected. Whitespace policy is enforced only in regards to indenting.

Tom
2009-10-14 19:24:41
Picking the most verbose palindrome example here's a more idiomatic python version:

max(str(i*j) for i in xrange(100, 1000) for j in xrange(100, 1000) if str(i*j) == str(i*j)[::-1])
Brett Hoerner
2009-10-14 19:39:39
First, I love both Python and Clojure, and completely agree that Clojure is more function and aimed towards concurrency (this was its goal, after all).  But I feel like you're doing Python wrong here.



<blockquote>The last print statement outputs "10" which is surprising to me, since y has never been declared in that given scope.</blockquote>



But it *wasn't* declared in that scope, there is no scope for 'if'.  It may not be what you're used to but you can't blame Python for your assumption of what should have it's own scope or not.



<blockquote>To resolve it you have to manually call 'del y'</blockquote>



What do you mean to resolve it?  Why are you deleting the reference at all?  You very rarely see 'del' in real Python code.



<blockquote> but I can't count the number of C app's that have suffered from memory-leaks and what not, based on the lack of automated garbage collection</blockquote>



What does that have to do with Python?  It is reference counted, those values will be deleted when they go out of all scopes, just like in Clojure.



<blockquote> But this is very different from Clojures concept where immutable means: 'x' never changes.</blockquote>



That isn't what it means in Clojure at all.

user=&gt; (def x 1)
#'user/x
user=&gt; x
1
user=&gt; (def x 5)
#'user/x
user=&gt; x
5

I just changed 'x'.  What immutability means is that the *value* 'x' points to does not change.  And that's *exactly* what it means in Python, too, but dictionaries *are* mutable (and this is documented).  For example, strings in Python are immutable, just as in Clojure.  If two things reference the same string and one "changes" it only means that it references a wholly new string.



<blockquote> Guido is not preparing to transition into modern programming</blockquote>



That's a little extreme.  Concurrent programming is valuable, but there will *also* be many programs that fit single process / thread models very well until the end of time.
Graham Fawcett
2009-10-14 19:41:56
"This helps them get to an amazing 29 lines for this simple problem and also makes room for quite a bit of error, although to their credit there is none."

Which is impressive, considering you have an error in the second line of your much shorter version. :) I think you meant (str %) not (str s).

I've enjoyed your articles very much, except for this one. Frankly you seem to be stretching. If you're going to compare Python vs. Clojure, you really should compare examples of the same algorithm -- it's more honest. For example, this version of Euler 4 in Python is as succinct as the Clojure version, uses the same algorithm, and runs in roughly the same time (from my wall-clock test):

print max(v for v in ((x*y) for x in range(100,1000) for y in range(100,1000)) if str(v) == str(v)[::-1])

 
Python and Clojure are both excellent languages. Both have tremendous Lisp qualities (you know that Norvig considers Python a Lisp, yes)? Having read your article, I'm left wondering what you're really trying to accomplish.
Lau
2009-10-14 20:17:50
Hi Brett,

Thanks for taking the time to leave a well-rounded comment, and letting me trim the whitespace bits which have been corrected.

I'd say you have a point regarding scope. It's not a deficiency in Python that Clojure is lexically scoped - But since I'm weighing Python against Clojure it's a relevant difference. I think most of my readers here are Clojurians and will be surprised by the scope in Python. 

In regards to immutability then yes, that it actually what it means, x doesn't change "while Im looking at it". You're not demonstrating an operation as in (conj, assoc, dissoc, etc) but a definition. When you have something which requires state you use something like Refs where change happens atomically on the STMs timeline. It's a completely different story than with Python.

And finally - You may think it's extreme, but I'm not so sure. There are many problems that aren't suited for the hack 'n' slash divison of labor Guide is proposing and more than that, I don't want to have to do it that way. For problems that require all CPU's running full speed, Python just doesn't seem like an option and that's a disqualifier in my book. 

Thanks, Lau
Jacques Mattheij
2009-10-14 20:22:55
The scope thing is a 'difference' but that does not make the one better than the other. Just like you have compiled languages and interpreted languages. 

Each to his own. You are doing clojure a disservice by the way as well, in the same way that perl and erlang zealots hurt their favorite languages. 

If you want to do a meaningful comparison between two languages you should start off with becoming proficient in both. And if you have a clear bias then you probably should not do comparisons at all, because you are not able to keep an even keel.
Brodie Rao
2009-10-14 20:24:29
You can execute on multiple cores with CPython by using the multiprocessing. It provides an API similar to the threading API but executes work across multiple processes.

I don't believe IronPython or Jython have any GIL-like restrictions, and Guido isn't averse to getting rid of the GIL in CPython if someone produces a reasonable patch for it. The Unladen Swallow folks are looking at doing just that, so don't be surprised if it goes away in the near future.

As it stands now, the GIL makes it much easier to write C extensions for Python. It isn't just there to spite you.
Brett Hoerner
2009-10-14 20:29:28
It'd definitely be correct to say that most things in Python are mutable and most things in Clojure are immutable.  I just think it's important to state that they do mean the same thing in both languages.  It's just that in your example dictionaries are mutable (and this is a well known fact for Python users).

I think the need for Clojure is a little more specific than just using all processors.  Python has a multiprocessing module that splits tasks into separate processes, so each can run at full speed.  Also, in the case of something like a pre-forking webserver where each request gets its own process, you're still able to make full use of a many-core box using languages like Python.  The need for something like Clojure comes when you need to be in the same process sharing things.  But there are lots of problems solved by shared-nothing and multiple processes.

I'd say use the best tool for the job, of course.  I've used Python for years and I'm actively learning Clojure for that very reason.
Lau
2009-10-14 20:34:16
@Jacques: Agreed on the scope as stated in last comment.

Regarding doing disservices please remember that this isn't a Python bashing post. Python has its place in the world, but generally you get more solid software out of functional code than imperative code, thats just the way it goes. When you can leverage multiple CPUs you get more power than when you only use 1 at a time. If you have a personal preference for Python and want to stay there, then I am not trying to steer you away from that. 

I just want people to be aware that there are alternatives, what they look like and why they might be for you. I don't think Python is a well suited tool for the coming challenges of concurrency in it's present state, but if you have a good reason to disagree that's fine with me. As a rule of thumb, you don't get even keels from blogposts you get them from API specs. But for those of you out there who want a fellow developers thoughts, here they are :)

/Lau
Lau
2009-10-14 20:42:27
@Brett: Generally I'd argue that Clojure should come into play when you want less bugs, more concise code and/or strong concurrency support. But that's when people start calling me biased :)

Like I think I touched on earlier the 'pre-fork' methodology isn't suited for all challenges and doesn't get you anywhere when you need shared memory concurrency. That's when people start experimenting with actors, web-workers etc. And although some of those approach might actually yield good results, they're all less efficient than non-distributed approaches. 

But of course, use the best tool for the job - Believe it or not, there are actually tasks for which I don't use Clojure.
Lau
2009-10-14 20:50:35
@Graham: Thanks a lot for stopping by.

So you caught me red-handed in a copy/paste accident - I've fixed it now :)

I agree with your sentiment that a same-algo vs same-algo would be more fair. But when reviewing programming languages, sometimes emotions run amok and so I feel it's best if I don't actually provide the code myself but take from some 'official' source or get help from the community. The reason I didn't conform the Clojure code to the official Python code, was simply because an incredible amount of computational models can be described using no more than: map, filter and reduce. So it's important for me to show of this model.
Graham Fawcett
2009-10-14 21:18:36
Hi Lau, my pleasure -- I'm really enjoying your blog.

I understand your points about using 'official' examples.

Note that Python also has map, filter and reduce, as well as list- and generator-comprehensions (which some would argue are more Pythonic than their functional counterparts, although they are operationally equivalent).  In Python 3.0, map/filter/reduce are almost identical to Clojure's, in that they consume and generate lazy sequences, rather than Python 2.x's which consume and output strict lists (Python lists, that is, which are more like vectors in Clojure).
Alen Ribic
2009-10-14 21:22:09
&gt; Python and Clojure are both excellent languages. Both have tremendous Lisp qualities (you know that Norvig considers Python a Lisp, yes)? Having read your article, I’m left wondering what you’re really trying to accomplish.

This is exactly what I was thinking when reading this article.
Jacques Mattheij
2009-10-14 21:24:50
&gt; Regarding doing disservices please remember that this isn’t a Python bashing post. 

You could have fooled me there.

&gt; Python has its place in the world, but generally you get more solid software out of functional code than imperative code, thats just the way it goes. 

I think that 'generally' that depends on large numbers of factors, not limited to just the languages involved, but including the quality of the programmer, the pressure under which the code was produced and so on. Sure, immutability is great to have (python has it too, just not in the way you seem to think it does), but that's no guarantee for not having bugs. A proficient programmer in either a functional or an imperative language will have their ways to deal with the limitations and to use the strengths of their chosen blade. 

&gt; When you can leverage multiple CPUs you get more power than when you only use 1 at a time. 

That's true, but in this respect python is simply a different beast than clojure, clojure is a little bit more fine grained than python, but still not as fine grained as it could be.

Parallelism under programmer control is decades old, python chose a different path. I agree that the stated reasons look a bit contrived, but if you really want the equivalent of threads then you could always import 'mmap' and go the long way around. 

Personally I'd prefer to break up the program in to different units that communicate using messages, but that's just my way of tackling these problems, and I'd probably do that in C, C++ or whatever language I would be using because I think threads are accidents waiting to happen, no matter what your environment. The only language that really seems to have this down pat is Erlang. Clojure vs Erlang would be a much more meaningful comparison by the way. 

&gt; If you have a personal preference for Python and want to stay there, then I am not trying to steer you away from that.

No, that is not fair. You have a personal preference for Clojure, I couldn't care less either way, but you do.

&gt; I just want people to be aware that there are alternatives, 

To python ? 

I'm sure that given the fact that python is still a very young language (as is clojure) that there are very few python programmers that are not aware that there are alternatives. If you want to give an unbiased comparison between the two then you could have done so, this is not a 'pointing to alternatives', this is simpy an attack piece pretending to be a fair comparison.

&gt; what they look like and why they might be for you. I don’t think Python is a well suited tool for the coming challenges of concurrency in it’s present state,

That may be, but you could have made that point in a way that it would have gotten a lot more traction and respect.

&gt; but if you have a good reason to disagree that’s fine with me. As a rule of thumb, you don’t get even keels from blogposts you get them from API specs. 

Not from yours, but I've seen people do that a whole lot better. 

&gt; But for those of you out there who want a fellow developers thoughts, here they are :)

Just out of curiosity, how many LOCs have you produced in Clojure and how many in Python ?
Nathan Youngman
2009-10-14 21:36:39
@Lau, if you review the comments I think you'll find that your audience is not unaware of problems with concurrency in imperative programming, and even the specifics of Python's GIL. We are the early adopters, the polyglots that have tinkered with many languages, and want to stretch our minds beyond the imperative.

I don't think "4 units when indenting" is yet accurate, the amount of spacing in Python just needs to be consistent from line to line. I found that after using it for a while, it was really no concern, which I'm hoping to be the case with Clojure's parenthesis.

@Brett made the true observation that many middle-tier web applications can work fine with shared-nothing, but equally it's worth observing that maintaining social graphs for the "realtime web" is but one use case for concurrency constructs provided in a language like Clojure.

Every language is working on a concurrency story, including Python as @Brodie mentioned. The beauty of Clojure is how the entire language conspires to make reliable software where concurrency is a non-issue.
Lau
2009-10-14 21:37:13
Erlang is another story, but it has an actor based distributed approach to concurrency. If you want to see that tried against Clojure read my blog post Scala vs Clojure: Concurrency. That'll also give you some insight on message passing in general. Saying that threads are a disaster waiting to happen is equal to saying "I don't know how to handle them safely", that certainly doesn't mean it's impossible - But Guido made the same mistake of saying that I think.

Try your hardest not to ignite a flamewar - they're tedious and unproductive.

/Lau
Morgan Gooose
2009-10-14 21:54:07
Read through the multiprocessing module's documentation. 
http://docs.python.org/library/multiprocessing.html

Python has multiple cpu utilization, stop saying that it doesn't.
Nathan Youngman
2009-10-14 21:54:36
Threads with mutable state are a disaster waiting to happen. We all agree.

The way I read Guido is that he would prefer to avoid exposing language users to the complexity of the typical locking model of Java/C#. The same as what Roberto &amp; Luiz say about Lua... they want to keep the language easy to use. Of course they have a much harder (near impossible) problem than Clojure, because of the baggage of mutable state.
Lau
2009-10-14 22:09:12
Morgan with all due respect, that document shows that Python supports multiple CPU's like a bicycle supports Formula 1 racing. That's exactly the kind of concurrency-tools you're running from when picking up Clojure.

/Lau
Jacques Mattheij
2009-10-14 22:37:40
&gt; Try your hardest not to ignite a flamewar – they’re tedious and unproductive.

I agree, so why do you continue to try to do just that ?

You start off with an inflammable subject, you use words that are clearly meant to fan the flames and they you tell other that they shouldn't ignite a flamewar ?

I don't get it.

But this is your page, you can do whatever you want here, I just think that if you intend to compare clojure to any other languages in the future that you should at least get a good feel for the other language and maybe code somewhere from 50 to 100K lines in it. Then you'll be able to make a real comparison.
Lau
2009-10-14 22:51:26
Simple - I have seen forums that were absolutely flooded in Python enthusiast quenching any and all mention of other languages - <strong>Everything should and must be done in Python</strong> was the motto! Similarily, when doing a blogpost such as this my mailbox gets absolutely pumped with mails from angry men who want to defend their castle. This is contrary to freedom of speech. 

I speak my mind because I'm hoping some of my readers will benefit and not have to spend more time than necessary in Python, C#, C++ or any other imperative language like I myself did. In the middle of a rain of Pythonic comments a Common Lisper demonstrated unique qualities of Lisp which caught my attention - Now I'm hoping to return the favor to a few more people.

If you seriously get angry and feel the need to defend Python that should tell you something. I don't feel the need to justify Clojure whenever it gets a bad review, people are entitled to their opinions - you just got mine.

Let that be the last word on that subject - If you have serious arguments to make for imperative, sequential, nicely indented code, I'd love to hear it.

Oh and not forgetting: If I write 500.000 lines of Python, does that make it functional? Will it be geared for concurrency? My experience however great or small does not change the main principles, which are my primary concern with Python, not the implementation details.
oldmoe
2009-10-15 00:14:48
There are different forms of concurrency, I/O concurrency and CPU concurrency, Python does I/O concurrency just fine, regarding CPU concurrency, Python's multiprocessing implementation makes it trivial to use multiple CPUs. 

You might not like the multi process concurrency model but this is exactly what Google opted for when they created Chrome. Their main argument was the higher cost of allocating memory in long running threaded programs due to excessive fragmentation.

It might not fit all concurrency problems but the multi process model is a very usable one and can be used for many problems.

It's not that I think everything should be written in Python (I don't even code in Python), I just don't like the way you dismiss using multiple processes for CPU concurrency. fork() will remain viable for a long time to come.
joe
2009-10-15 02:24:27
I implemented the 3 euler probs in ruby:
http://codepad.org/GFeDLEum

maybe it's just me, but I find them much more readable than clojure or python.
Paddy3118
2009-10-15 02:47:59
If closure is good then why don't you let it speak for itself? 

Lurk, then join Rosetta Code at http://rosettacode.org and add your examples to the tasks so people can compare them to the other tens of languages solving the same tasks (http://rosettacode.org/wiki/Category:Solutions_by_Programming_Task)

- Paddy
jneira
2009-10-15 08:01:31
And what about lazyness and macros (homiconicity, "code as data" related). Has Python a direct way to manipulate the code syntactically?
Lau
2009-10-15 09:37:28
@jneira: Python isn't homoiconic and has no macros. It does have decorators which seemingly can emulate macros in a small way.
Graham Fawcett
2009-10-15 15:31:43
No, decorators are higher-order functions, mapping functions to functions. They are a declarative syntax for transforming functions, at their point of definition, to add new capabilities. See http://www.python.org/dev/peps/pep-0318/#motivation .

While Python isn't homoiconic and has no macros, it does have 'eval', code-generation facilities, and an import-hook system that can allow for syntax extensions. It's not nearly as straightforward as the Lisp-macro story, but such things are possible.
Py
2009-10-16 01:57:41
http://blog.hackers-cafe.net/2009/10/re-python-vs-clojure.html
Lau
2009-10-16 09:58:47
<blockquote>Lisp qualities (you know that Norvig considers Python a Lisp, yes)?</blockquote>
 
@Graham: This comment you made stuck out, because I actually thought Peter Norvig was an quite intelligent individual and wondered how he could make such a mistake, so I went and looked for documentation and this is what he says:

<blockquote>Python <strong>can be seen as</strong> a dialect of Lisp with "traditional" syntax (what Lisp people call "infix" or "m-lisp" syntax)</blockquote>

And that's true, like a bicycle can be seen as a slow airplane. He also goes on to say that Python has most of the features of Lisp, except Macros. Macros isn't a 'little' feature you add at the end, it's a powerful abstraction tool unifying programming and meta-programming. It's game changing.

/Lau
Kfir Breger
2009-10-16 10:39:02
Mr Jensen,
Let me start by saying that I found your article interesting to read. You write about Clojure with passion that is a pleasure to read.
That said I would like to say that although your intention was probably need Python bashing, you ended up doing it quite well. You do not claim that "I prefer language x to y because I prefer the way it handles z", but simply that "Language x is better then y because y's way is not good". There are quite a few clever people working on these languages, and it does then no justice to be so dismissive of their work. As you stated, choices are often a trade off. In some cases it is so that a choice turns out to be far worse, but then evolution kicks in and that language fades. Most of the time it is a question of personal preference, which is why it is wonderful that there is such a range of choices.
Another point I would like to make is that an easy language as you call python is actually good for reducing bugs, which you claim Python will have more of. From my experience, the easier the language is to work with, the less bugs its code will have.
All that said I it was good to read your opinions from criticism comes improvements.
Lau
2009-10-16 10:52:44
Mr. Breger,

Thank you very much for leaving a comment. I can assure you that my intention was in no way Python bashing, as I also state in my conclusion there's a place for Python in development. I would urge both you and others not to view this as a dismissal of all the hard work that has been put into Python, I realize full well the amount of energy that must have taken. That amount of energy however did not produce a functional, safe language with strong concurrency semantics which makes it more bugprone than others, that's the point here. So for some this is a caution not to bet all your money on Python and to others its an encouragement to seek new challenges. I hope that comes across.

Regarding getting rid of bugs. The vast majority of bugs I've seen in real-world applications are simply this: Unexpected state. That is a challenge which comes with imperative languages such as Python. If anything an 'easier' entry-level language attracts developers with lesser skills, which then take these volatile tools and produce a multitude of bugs. Consider PHPs story for instance.

Thanks a lot for your comment :)
/Lau
Graham Fawcett
2009-10-16 15:59:53
"And that’s true, like a bicycle can be seen as a slow airplane." Heh, you know how to get your feelings across, Lau. :) 

Well, I agree macros aren't a little feature, and that they are certainly one of the classic features of a Lisp. But as Norvig wrote in the same article you quoted ("Python for Lisp programmers"), "... you don't miss macros all that much because [Python] does have eval, and operator overloading, and regular expression parsing, so you can create custom languages that way." The real joys of Lisp programming, for me, have only occasionally been in macrology, and I've had 'near-Lisp experiences' in Python many, many times.

I read Norvig's observations on Python favourably. It may be that, subconsciously, I hold your blog article here in comparison against Norvig's, which is still the best comparison of those two languages I have come across. What I like about his article is that, whether you are a Pythonista or a Lispnik, he makes the other language seem worth investigating.

I really hope that more Python users will discover Clojure. I also hope that all Clojure users will continue to learn other languages -- even if only to draw inspiration from them, as Rich has so artfully done in designing Clojure. I am thankful that PL's are not a zero-sum game, or we would all still be writing Fortran. Vive la diversité!
Lau
2009-10-16 21:23:52
I might not fully understand what Norvig is saying in regards to eval & co. but I think you'd be hard pressed to emulate macros in any meaningful way, but before I pass swift and cruel judgement on Norvigs Macro skills, perhaps someone could dedicate a blog post to macros vs pythons-eval & friends? *hint* Graham *hint* *wink*. It would certainly be very enlightening, especially for Python users who have yet to wield the power of Macros.

I agree that Norvig has done a much better comparison, politically speaking. My guiding principle is always this: I will be held accountable for what I write. I don't want some developer coming back in 6 months being upset with me, for leading him down the Python path into all the troubles that come with mutable state, lack of concurrency support, bad performance characteristics etc etc. 

I'm confident in most of Clojures abilities and through several posts here I've tried to outline them to a degree where I'm not worried that I'm leading people into trouble, no matter who is reading. But I can't send a slightly-above-average developer into a game of locking Jython threads and be confident he won't come back and tell me that he was held up 1 month because of dead-/livelocks and when he solved that, he got stuck on starvation.
Graham Fawcett
2009-10-16 22:00:52
Heh, I might try my hand at an article some time, my blog is very, very stale. But in case I don't get there, I just want to clarify what I meant, and what I think Norvig meant. It's not that 'eval' can be used to emulate macros; rather, that there are other qualities of Lisp (shared by Python) which are more central to Lisp than macros.

I'm sure you've encountered new Lispers in news groups, asking how to solve some Problem X using macros. Experienced users will often reply that macros might not be the right (or best) answer to the given problem -- often, for example, a higher-order function that makes more sense, and leads to more composable code in the long run. 

It was certainly true for me when I first came to Lisp -- I was writing macros left and right, and found over time that many of my macros were less manageable than their functional alternatives. I learned to save macrology as a 'big gun', for when new syntax really added power, not just to save a few keystrokes. (As an example of what I mean, consider how many syntactic Common Lisp features are replicated in Scheme as HOFs -- contrast (with-open-file ("foo" ...) (format foo "hello")) with (with-output-to-file "foo" (lambda (f) (write "hello" f)) .)

I'm not dismissing the value of a good macro -- I'm just saying that it many cases, macros just provide a sugary coating over fairly straightforward, functional code. That is, functional in the closures-and-lambdas sense. (Having a powerful functional language that supports partial evaluation, currying, etc., opens even more opportunities for succinct code without macros.)

So, if I may boldly interpret Norvig, I think he is saying that if you have 'eval' -- broadly, if you can generate and interpret/compile code at runtime -- you have one of the best parts of Lisp. (Having lambdas and closures, you have another.) In short, since Python has 'eval', it has the heart of Lisp. 

Moreover, while Python isn't homoiconic, it does have a lot of flexible syntactic sugar and metaprogramming facilities that can be used to design some very expressive DSLs. In many cases, reusable syntax is nearly as good as programmable syntax.

I'm ambivalent on the 'leading people down the wrong path' argument; I see your point, but I've read too much good Python code to dismiss Python outright as a bad choice for a broad range of programming tasks. But still, I have my eye on Clojure, and know I will encounter problems for which it is far better suited than Python or any other language.

Whew! Sorry for rambling, I probably ought to have written a blog post after all. :)
Nathan Youngman
2009-10-17 08:31:09
Besides eval(), high level languages like Python and Ruby provide access to the AST, and have hooks into code execution. Functionally equivalent to Lisp style macros, but I can't imagine being nearly as straightforward as defmacro.
Nathan Youngman
2009-10-17 08:32:03
This I did not know about Python 3. Thanks Graham!
Nathan Youngman
2009-10-17 09:07:04
Lau,

What I've appreciated most about this series is the code comparisons that help me grok how Clojure accomplishes the equivalent to what's in another language. It was unfortunate that the Python examples from Project Euler were rather poor compared with what can be done, as @Py's linked example. 

The GIL is not unique to Python. Ruby also has a Global Interpreter Lock. Personally, I think it would come across better to say "imperative languages are doomed" when it comes to concurrency. They generally are :-). You may find it enlightening to read Guido's article on how Python was never intended to be functional:

http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html

Python is 20 years old this year, where as Clojure just turned 2. Obviously Python wasn't designed for multicore like Clojure was. I can't imagine even Python 4 doing away with mutable state and breaking every library ever made. That's why we need new languages to solve new problems.

You had mentioned researching Haskell? It might be interesting to compare Clojure's lazy sequences to Haskell's full on lazy evaluation. The problem solving articles like the Dining Philosophers were also very good.

- nathan
Paddy3118
2009-10-17 10:47:56
Hmm, 
I am just remembering one of the debates about the inclusion of macros in Python. It is not that it cannot be done. Someone created their own Python dialect with macros. The reason it is not in Python is to do with the ethos of the language designers. Maintenance and readability is a massive motivator for the Python team, and with macros you would have the ability to create your own statements that make a program harder to read.

When you read a Python program, you know what the statements are. No one can redefine them or add new statements - This aids readability.
When you read a Python program, the indentation matches the logical structure of the program. Always!

On someone's comment that Closure is functional, without mutable values, and so safe: To make any functional language usable for real-world problems, those functions must have side-affects which break their purity and any safety guarantees - welcome to the real world.

On the criticism of Pythons speed: Execution speed is an optimization, and should be treated as such. Pythons language design aids in getting 'it' right. execution speed is often not the top priority in a programming task, and is often not known until you get the underlying algorithm right. There are articles out their that state that time to market - i.e. development time is key for any product, and that maintenance takes more effort than initial development. Pythons designers take this into account.

Python is not slow. Especially if you factor in development time and maintenance effort which takes the most time, and effort in a project.

Python can be faster than C/C++ I have blogged about such a case where use of Pythons highly optimised built-in set and dict data-structures leads to a program that ranks regression results in a mucch faster time than that given by a commercial tool written in C++. 

On your statements of Clojure's brilliance, you really should join one of those language comparison sites and let the language shine. Unfortunately it is hard, as you have shown in your Python examples, to be equally proficient in multiple languages to make a one-person comparison effort realistic. Better to submit your best Clojure examples on a site like Rosetta Code.

- Paddy.
Paddy3118
2009-10-17 10:51:40
P.S. - I'm not angry. I know it is hard to judge mood on a forum like this so I think I'll add this in longhand :-)

- Paddy.
Lau
2009-10-17 20:08:22
Hi Paddy, thanks for checking in =)

On Macros: Notice again how the argument is 'ease' and not quality. I agree that you can easily implement so much customization that the complexity goes through the roof (C++), but Macros is pretty far from that border. Although I completely agree with Grahams sentiment, the first rule of Macros is: Don't write them. Second rule is similar: Dont write them, if a function can do the same. The rest you can read in 'On lisp'.

Regarding letting Clojure speak for itself you're not the first to have suggested that, so I've added a Clojure solution to the Lehman Mersennes challenge, weighing in at 11 lines vs Pythons 31 :P  Check it out here: <a href="http://rosettacode.org/wiki/Lucas-Lehmer_test#Clojure" rel="nofollow">Rosetta Code</a>

/Lau

Best regards, Lau
Andrew Dalke
2009-10-20 14:27:52
Your solution yields '99999' instead of the expected 906609 because you ended up doing the max string instead of the max integer and '99' &gt; '906'. Try

max(i*j for i in xrange(100, 1000) for j in xrange(100, 1000) if str(i*j) == str(i*j)[::-1])
Daniel Sobral
2009-10-30 04:57:03
"I read somewhere a staggering statistic, that the seniority and experience of developers wasn’t directly related to the quality of their code!"

You probably read Steve McConnell misquoting Sackman, Erikson &amp; Grant paper on programmer productivity on online and batch enviroments.

His quote appeared here: http://blogs.construx.com/blogs/stevemcc/archive/2008/03/27/productivity-variations-among-software-developers-and-teams-the-origin-of-quot-10x-quot.aspx

I'll quote him: "They found no relationship between a programmer’s amount of experience and code quality or productivity."

The paper actually shows the opposite, as might be expected. What the study actually says is that they found no correlation between programmer's experience and the VARIABILITY of the measured quality/performance. Ie, experience does not makes quality/performance more uniform.

More precisely, the 20x difference in productivity they found between best and worst programmers was to be found both among experienced programmers and among beginner programmers.

That paper was bare bones and had many methodological problems. But what it really pains me is that someone like Steve McConnell can so horribly misquote it -- I don't even know if by mistake or by misunderstanding -- and everyone keeps repeating it as if it were truth. :-(

But don't trust me. There's a freely available scanned copy of that paper on the Internet. Download, read, and make your own mind.
Lau
2009-10-30 10:01:09
@Daniel: Hey, thanks a lot for stopping by - I'll have to dig a little deeper to uncover the truth - if it ends up that you're right I'm very happy that you corrected me, thanks!
Tota
2009-11-03 11:51:14
Hi all,
Cheers for ur great comments. I wanna add mine too...
1) Python is good for a lot of tasks but is not universal panacea 
(zealots claim its panacea and try to solve all problems with it, which is bad)
2) I need to see wide libraries in clojure catering people in different domains (eg.graphics, web apps etc). Also, to add I hate using java with clojure for libraries sake.
3) Have anyone thought of PHP + Clojure combo?

MTC,
Tota
Lau
2009-11-03 11:53:52
@Tota:
1) Agreed, ditto for Clojure :)
2) They're already there, go look - whats missing can quickly be added - we have macros remember?
3) No. I wouldn't think that to be a terrible cocktail! I've had much success driving websites and services using Compojure.

/Lau
Nick Coghlan
2009-11-23 13:40:12
If comments are moderated, it's polite to include a notice to that effect so folks know not to expect their comments to appear immediately...
Lau
2009-11-23 13:45:07
@Nick: Hey Nick. All commentators are notified directly after posting via email, that I'm reviewing their comment, secondly you should see a small notification in the lower right of your comment that it's awaiting moderation. Lastly - I don't like to moderate but my dislike for nonconstructive comments is greater, so I must. If you have submitted a comment free of swearing, cursing, ridicules etc, it should be approved quickly :)
Nick Coghlan
2009-11-23 13:51:27
There was actually a long comment I submitted prior to the one about moderation. It didn't appear *at all*, but attempting to submit it again brought up a "duplicate submission" notice - hence why I complained about a silent moderation queue. I'll give it another go with that message in two parts
Nick Coghlan
2009-11-23 13:51:52
(Just received a link to this article this evening)

This article and your next one are fairly interesting, but they do reveal that there is still plenty for you to learn about Python, and that the folks writing the algorithms for the comparison sites either aren't very good at writing (or aren't trying to write) idiomatic Python (PEuler examples in this post) or else are likely to be optimising the code for different things based on the ethos of their language (Rosetta examples in the next post). In the latter case, Python emphasises readability and maintainability over merely being concise. Sometimes those goals align, but when you try to get too clever in cutting the code size down, you just end up creating something cryptic and unreadable for the sake of shaving off a few characters or lines. For a language that prides itself on being "executable pseudo-code" stopping short of that point is generally seen as desirable, and this is likely to come through on code comparison sites. As Guido has pointed out many times, code is typically read far more times than it is written, so readability counts for a lot (and sometimes the poor sap that has to understand what the code is doing is you a couple of years later, so writing readable code often benefits the author as well).

Anyway, clarifying a few issues with your general understanding of Python:

On scope: Python is not C++ (where every pair of braces creates a new scope). Only class and def  statements create new scopes for local variables in Python (generator expressions, and in Python 3, list/set/dict comprehensions, are a special case within a single expression where they create a new scope for their iteration variables so they can't overwrite variables in the containing scope). This really isn't all that confusing, and aside from the Python 2 problem with list comprehensions overwriting variables in the surrounding scope (which is an acknowledged design flaw that has been fixed in Python 3), I've never seen anyone (even relative Python novices) make a mistake due to it.

On indentation: despite the note at the end, the text of the article still claims that Python enforces a 4 space indent. While that is what PEP 8 recommends, the interpreter itself doesn't actually care how many spaces or tabs you use, so long as you're consistent (and at least use *some*).

On mutability: you somehow got Python's definition of immutability completely backwards. The only immutable builtin container types are tuple and frozenset - once created, these cannot be changed. Just like numbers and strings (and bytes in Python 3), you can only alter your reference to point to a different one, leaving other references unaffected. The other builtin containers (list, dict, set, and bytearray in Python 3) are all mutable - alter them through one reference, all other references to the same container will see your changes. (Obviously, you can also switch a reference to a mutable object to point to a different object, leaving the object itself unchanged)

On lambda: Gah, no, NEVER assign a lambda directly to a variable name. That's what def is for: "def evenp(n): return n % 2 == 0". This gives evenp.__name__ the right value, gives you better tracebacks, lets you add a docstring easily (if the function is one that deserves it). Using lambda instead of def in this situation is a classic sign of someone that is trying to use Python to write in a different language (usually one where *everything* is an expression, so the idea of using the appropriate statements doesn't always occur to them).

On the GIL: The GIL is a CPython only problem, and it only exists because we refuse to sacrifice single core performance (which affects *all* Python applications, since they all initialise single threaded) in pursuit of faster multi-threaded performance for a particular class of application (i.e. those with CPU bound threaded workloads, a style of application writing many of the core developers, including me, have fundamental issues with). If it really bothers someone, they can use Jython or IronPython instead, since they don't have a GIL (by virtue of running on the JVM and .NET CLR respectively) and will happily distribute your CPU bound threads across as many cores as your machine happens to have. IronPython is a particularly interesting choice, since Ironclad will still let you use many CPython extension modules. Or, even better (and our recommended approach), use multiple processes and an IPC mechanism, since you can actually scale that to not only multiple cores, but also multiple devices with no shared memory.
Nick Coghlan
2009-11-23 13:58:56
And since I can't resist... cleaned up implementations of the presented Euler problems:

Multiples of 3 or 5:

print sum(x for x in xrange(1000) if x % 3 == 0 or x % 5 == 0)

(Note: the posted implementation was just plain wrong - it included 1000, when the problem spec excluded it, and the introduction of the "multiple" function not only used poor coding style, but was also a pointless waste of code on the page and execution time)

The fibonacci code is horrible too. It looks like someone just transcribed a Lisp solution instead of using the appropriate Python tools (specifically, a generator function). Here's a better version:

def filtered_fibonacci(limit):
  a, b, c = 0, 1, 1
  while c &lt; limit:
    if c % 2 == 0:
      yield c
    a, b = b, c
    c = a + b

print sum(filtered_fibonacci(4000000))
Nick Coghlan
2009-11-23 13:59:23
Exactly the same complaint about the last example as the fibonacci one: trying to hammer a functional solution into Python code, when a more appropriate tool is a generator (which you appear to mention, but then don't post, instead preferring to present the obfuscated I-wish-I-was-actually-writing-Lisp functional approach).

def palindromes(limit):
  numbers = xrange(limit // 10, limit)
  for x in numbers:
    for y in numbers:
      n = x * y
      sn = str(n)
      if sn == sn[::-1]:
        yield n

print max(palindromes(1000))

The "sn[::-1]" expression is a standard Python idiom for reversal of a sequence (it is a start:stop:step slicing syntax, where omitting the start and stop location implies using the whole sequence, and the negative step implies starting at the end and counting backwards). The more verbose "str(reversed(sn))" is less cryptic for Python novices but much slower to execute.

(Just for the record, the significant line counts are: 1, 8, 9, and the last two could trivially be reduced to 7 and 8 by writing the if statements as one liners. The first comment shows that the third solution can even be reduced to a one-liner at the expense of making the code less self-explanatory)
Nick Coghlan
2009-11-23 14:00:54
Now, where the significant indentation for Python does get annoying is all the web comment forms that strip leading whitespace. I blame the web forms for that rather than Python though ;)
Paddy3118
2009-11-24 05:50:50
Um, Nick,
I think i can see from the context just what you are trying to say, but your use of the word concise in the sentence 

"... Python emphasises readability and maintainability over merely being concise."

Might be wrong as the meaning of concise is "giving a lot of information clearly and in few words" which I think is what Python is good at. (Definition from: http://www.askoxford.com/concise_oed/concise?view=uk)

- Paddy.
Nick Coghlan
2009-11-24 14:10:15
There's a line between "concise" and "cryptic" that depends on the abilities of the *reader* moreso than the abilities of the author. So it's a nuance thing: the dividing line between "concise" and "cryptic" depends on the audience you're trying to talk to.

Take my "sn[::-1]" for example. That's idiomatic Python, so I'd use it in production code without a second thought, but here, with an audience that includes folks that aren't Python programmers, I felt it crossed the line into being cryptic, so I took the time to explain it.

I have the opposite problem when I read functional code - while I can handle functional code if I have to, it isn't my natural way of thinking (I'm more a communicating-sequential-processes kind of guy), so code that seems perfectly clear and concise to someone used to thinking in a functional style seems unncessarily cryptic to me.

So what I meant with that phrase was that many (but certainly not all!) Python programmers prefer to err a bit more on the side of writing easy to understand code, even if it means making the code slightly less concise. Of course, other considerations may trump that tendency in particular cases, but it is a pattern I have seen often enough to consider it a trend. That's just an opinion based squarely in the realm of mere anecdotal evidence though.
Tota
2009-12-03 06:18:57
Hi Lau,

I initally wrote 3 points in the blog and u addressed them cheerfully.

But, I wanna say one new thing:
"The clojure bug has bitten me hard".

I have a gut feeling that clojure may hit the lime light one day and stay for years together. Scala is overly complex and will fail definitely.

Well since, the bug bit me hard, I am gonna to first re-write some parts of my project in clojure. If that proves successful, all my new projects, "will be in pure clojure".
Lau
2009-12-03 09:08:08
Hi Tota,

Glad to hear that you're diving into Clojure and enjoying yourself. Look forward to seeing some quality software coming from you :)

/Lau
angel
2011-06-18 03:03:45
I don't know why.but I always read benchmarks and LOC from http://shootout.alioth.debian.org/

and always clojure has more LOC than python , ruby and even javascript!!...