HomeBlogThe snake, seen from the eyes of the Camel

The snake, seen from the eyes of the Camel

— or “Python at first sight for a Perl programmer” —

I spent some time with Python and thought I'd share some opinions.

In short, my conclusion is that Python is a great programming language and I'll definitely invest some time into it: it has a clean syntax, it's very fast (2x compared to Perl), it has lexical scope (which is the only kind I care about, but see below), true objects, first class functions.  It has its share of downsides and I hope I'm expressing them well here.

I welcome any Perl and Python experts to state their opinion on the matter, and maybe fix/optimize my code if this is possible.

The Study

To make a good idea of the language, I needed a problem.  I chosen to reimplement Chess::Rep, a Perl module that I wrote for my online chess game, in Python.  I did my best to translate it almost ad literam, but I realized that Python had some nice features that Perl didn't have — such as the "in" operator which is cooler than I thought — so I ended up using some particular Python features.

Describing some conclusions below, then you can jump to the code.

The good parts

  • Python has a nice "in" operator.  What is so nice about it is that it works on all the standard data types.  For example, no matter if something was an array, a dictionary or a string, the following does what you expect:

    if "Q" in something:
        print "found it"
    

    When something is an array, it returns True if it contains a "Q" element.  When it's a dictionary, it will return True if a "Q" key exists.  Finally, when it's a string, it will return True if that string contains the "Q" letter.  In Perl we need a different version for each case:

    # array
    if ( grep { $_ eq 'Q' } @something ) {
        print "found it"
    }
    
    # hash
    if ( exists($something{Q}) ) {
        print "found it"
    }
    
    # string
    if ( index($something, 'Q') >= 0 ) {
        print "found it"
    }
    
    # and I leave out the case when $something is a
    #     reference to an array or a hash
    

    I would have nothing against it; in fact, the Perl version can be regarded as somewhat better because you know just by looking at the code what type of variable something is.  However, it's actually harder to see what the code is trying to achieve, and that's usually what matters.

    The "in" operator saved some typing in the Python version of my Chess module, not to mention the result is elegant and readable.  White ball for the snake.

  • Similar to the "in" operator, I loved the fact that strings can be indexed or sliced as arrays.  Python version:

    a = "qwer"
    print a[0] # prints "q"
    b = a[1:3] # "we"
    

    Perl version:

    $a = "qwer"
    print substr($a, 0, 1) # prints "q"
    $b = substr($a, 1, 2)  # "we"
    
  • OOP.  Classes, inheritance, methods, modules, exceptions.  We have all this in Perl, but it's like an afterthought.  Although I learned to use OOP in Perl quite well, this doesn't mean I like it.  There are too many ways to do it.

  • Generally cleaner syntax.  This can be noted even from simple examples, like the ones above.  This comes with a price, though, at least for me, a Perl guy, as we'll see below.

  • Fast.  In my test (which I believe to be a real world test, I'm not just adding some numbers a million times) Python was 2 times faster than Perl.  2 times is a lot.

The bad parts

Lo and behold for here I start...  Here's the stuff I dislike.  I talk about them in many words because if I simply said “I want anonymous functions”, most Python folks would ask me “what for?”.  I don't mean to criticize uselessly; I truly like this language and despite whatever I wrote below, I think it beats Perl and I'll try to use it for my future projects.

Missing sigils

The first impression is that the missing sigils improve readability.  That's even for one with a solid Perl background.  However, on second thought, it wasn't such a good idea.  If I had the option, I would prefer to name all (or most) my variables starting with a $ sign.

In my little Chess program I frequently needed to use a variable named from.  This name simply makes sense in the context of my application.  If Python allowed sigils, $from would have been an acceptable name.  But it doesn't, and for some reason from is a keyword.  Bummer — we can't use it to name our variables, although as a keyword, from is only allowed in the preamble of the program anyway (I think this is a new restriction).  I had to chose between "_from" and "from_" in my application and I preferred the latter for that particular case.

Again: I would prefer, if I had this option, to prefix my variables with sigils.  "$" would be enough (I don't really need % and @).  I'm quite sure this can be done without breaking existing code, but it's unlikely to happen.

No anonymous functions

This is inherent in the language syntax.  They introduced the lambda construct as a way around this, but lambda is only for one-liners.  Example of what I hate:

def valid(x):
    if x & 0x100:
        return True
    try_move['to'] = x
    return not self.is_attacked(x if is_king else king, op_color, try_move)
valid_moves = filter(valid, moves)

In a hypothetical “Mython” language, which I like more than Python, I could write it like this:

valid_moves = filter(lambda(x){
    if x & 0x100:
        return True
    try_move['to'] = x
    return not self.is_attacked(is_king ? x : king, op_color, try_move)
}, moves)

How hard can it be?  I'm pretty sure that it wouldn't break compatibility with existing code (simply because existing code doesn't use brackets this way).

Yes, it would require those little brackets, which is against the spirit of Python, but I think people wouldn't mind.  Don't use it unless you want to.

Will it happen?  Probably not, if you read this discussion and it's very sad.

[ heh, it looks like Mython actually exists and of course, it's about Python.  I coined the name without knowing that ]

Implicit variable declaration

This can lead to a series of problems.  First off, I quickly learned that from a closure it's impossible to change the value of an external variable.  The following doesn't do what I expect:

def foo(a):
    def bar(b):
        a += b
        return a
    return bar

f = foo("abc")
print f("def")  # <-- error here
                # local variable 'a' referenced before assignment

This happens because variables are implicitly declared.  Here's a version which is more clear:

def foo(a):
    def bar(b):
        a = a + b
        return a
    return bar

f = foo("abc")
print f("def")

(same error).  When it sees "a =", Python declares a local variable (local to the inner function, that is) and then complains that you try to use it before it was first assigned.  The  following is the "correct" variant, in terms of Python:

def foo(a):
    a = [ a ]
    def bar(b):
        a[0] += b
        return a[0]
    return bar

My suggestion for a fix is to follow other languages — add a syntax that declares variables, such as "my" or "var" — whatever you like.  This way the interpreter could safely assume in the following code that we meant to access the outer "sum" variable.  I think it wouldn't break compatibility either.

def foo(a):
    var sum = a
    def bar(b):
        sum = sum + b
        return sum
    return bar

Of course, the best version IMO is to follow JavaScript — when you assign to a variable, it will actually use the closest variable with that name from the enclosing scope; if none exists, then create a new (local, instead of global as JS) variable.  However this could potentially break existing code.

Conditional ("ternary") operator

I'm pretty sure that even for regular Python programmers, this must seem weird:

a = some_value if some_condition else other_value

The excuse given by the Python gurus was that most of the times when you write this, you'll naturally arrange things such that some_condition will most of the times be True and some_value will be most of the times returned.  This is the lamest explanation ever and it's totally untrue — in most cases you can't know, not even give a close estimate, of how many times will be the condition negative or positive.  And it looks plain ugly, compared to how every other language on Earth has it:

a = some_condition ? some_value : other_value

I found some nicer possibility with Python, based on the fact that you can index tuples.  Pretty cool:

a = (other_value, some_value)[some_condition]

When some_condition is False, the first value is selected (False translates to zero); when it's True, the second value is selected.  The only problem here is that some_condition might not be a boolean, in which case you'd have to use one of the following:

a = (other_value, some_value)[1 if some_condition else 0] # bwuhahaha
a = (some_value, other_value)[not some_condition] # this is better

In any case, not having the ubiquitous "?" / ":" operator, which everyone knows, is L.A.M.E.

Missing qw()

When you want to write an array of words, it feels natural for an old Perl hacker to write:

qw( it feels nice to write it this way )

The Python variant is uglier:

( "i", "hate", "to", "write", "it", "like", "this", "honestly" )

Fortunately, everything is an object in Python, so you can make it bearable:

"this one is a lot better".split(" ")

but be extra-careful about whitespace :-p.  The string's split method does not take a regexp.

Expressions vs statements

One thing I would do from time to time is mix statements with expressions.  In all languages that I used so far, an expression would return a value.  For example I instinctively wrote the following code, while translating my Chess module:

for step in MOVES_B:
    j = i
    while not test(0x28, j += step):
        pass

It doesn't work because j += step is a syntax error in that place.  So I had to write it like this:

for step in MOVES_B:
    j = i + step
    while not test(0x28, j):
        j += step

No big deal, but I don't like the fact that "+ step" appears in two places.  By the way, in Perl the above is expressed using the "do ... while" loop:

for my $step (@MOVES_B) {
    my $j = $i;
    do { $j += $step }
      while (!$test->(0x28, $j));
}

The do ... while loop is intended for cases where you want to increment first, then check the condition.  Too bad Python doesn't have it.  (Yes, I know the variant with while True / break and it's lame.)

Hash dictionary key names must be quoted

That's not a big deal, but it would be nice if you could write literal hash constants without having to quote each key name.  JavaScript got this right — quoting is optional when you define the hash (unless you use some keyword) and it's mandatory when you access it (as in a["foo"]).  The rationale is that when you define it, you almost never want to use the value of a variable for a key.

Block exit functions (or "goto")

Perl has this nice concept of named blocks — so you can exit any of the enclosed blocks instantly using last or next, or use redo to repeat a block, or generally use goto to jump to a certain point.

Wait, before you tell me that goto is dangerous and shouldn't be used, please go and tell someone else.  I'm doing programming for almost 20 years.  I know that some tools are dangerous, but I know how to use them.  This doesn't mean that my program is full of goto-s (I think it's been more than a year since the last time I wrote "goto" ;-) — but I'd just rather have the option than not have it.

An axe is a dangerous tool too, but sometimes useful.

No CPAN

Time and time again, I realize that this is Perl's advantage number 1.  I wish Python had such a great network of (good and free) modules.  That's not Python's fault, it's rather the community and it'll probably improve.

You can get the code and/or test it now.

Comments

Page info
Created:
2009/02/05 20:29
Modified:
2009/08/25 17:47
Author:
Mihai Bazon
Comments:
2
Tags:
perl, programming, python
See also