Python impressions

June 6th, 2008 at 11:42 am

Introduction

Update: A few words on my programming background. I’m most proficient in C and Perl, with C++ coming closely behind. Additionally, I’ve programmed in Ruby, Lisp, Scheme, Matlab and Ada. More on this here and here.

About 3 weeks ago, I’ve decided to give Python a try. During this time I have been intensively learning and using Python, so now I’m ready to give a list of my initial impressions.

First, a word on my learning process. Python is a mature and useful language, so from the start I decided not only to play around, but to take it quite seriously. So, apart from reading the official Python tutorial and most of the free online book Dive into Python I spent a lot of time actually using Python, both for writing test scripts that display various features I might need and useful programs, both at work and home. Here’s a rough list of the code I’ve written to get familiar with Python, in no particular order:

  • Accessing Windows DLLs from Python code
  • GUIs with wxPython
  • Tools for working with files, paths and directories
  • Code involving threads and sockets
  • Various scripts to test how Python finds its modules and packages
  • Used py2exe to package scripts as self-contained .exe files

I’ve also written a couple of non-toy applications that actually do something useful:

  • wxPyTris – a Tetris clone
  • A simple GUI that can work as a XML-RPC client or (threaded) server
  • A netlist / pinlist comparison utility useful for my work. I had it previously written in Perl, but now I’ve written a more feature-complete version in Python with a wxPython GUI

In total, I think I’ve written at least 3K lines of Python code and read a lot of documentation, newsgroup posts and tutorials. I’m still far from being proficient in Python, but I can now use it quite effectively.

My Python impressions

The impressions are divided to positive and negative, and inside each section there’s no particular order.

What I like about Python

  • Significant indentation is actually a pretty good thing. Since I always pedantically indent my code anyway, I don’t find it to be an impediment. On the contrary – it has some very desirable features: much less punctuation to type (braces, begin/end, etc.), and code written by others is usually easier to understand, because Python forces it to be structurally cleaner.
  • In a way, Python is openly supported by a large company that uses it extensively – Google. This means that there are a few smart people who get paid to develop and improve Python. For example Guido Von Rossum, the Python creator, works on Python in Google. I heard that the “release manager” for Python 2.6 also works for Google. This is important: while enthusiasts can create great tools in their free time, my experience tells me that to really perfect a tool, consistently, it is important for some people to earn their living from working on it.
  • The Python community doesn’t treat Windows as a Nth-priority platform. All modules I’ve seen so far come with easy installers for Windows that just work. This is very important for me, as 95% of my work is being done on Windows.
  • The documentation is very good
  • PyCrust is a very helpful tool
  • wxPython – very mature and easy to use. The wxPython demo is amazing. Everything works very quickly, with beautiful native GUIs. wxPython is so good that most open-source Python IDEs seem to be written in pure Python + wxPython
  • Convenient built-in named arguments
  • Docstrings
  • The in operator
  • List comprehensions
  • Chained comparisons in expressions: (1 < x < 4) instead of (x > 1 and x < 4). Remember that this feature really shines when you replace x by self.server.count_connections() :-)
  • Batteries included – the Python standard library has a lot in it, and this is a very good thing, especially when you want to quickly distribute your scripts to coworkers who have no idea about Python.
  • The way the print function works is quite convenient – it adds spaces between its arguments and a newline at the end, and thus saves boilerplate typing when debugging. And let’s face it, print in Python is used mainly for debugging anyway (files are written with the write() method of file objects).
  • Python has native threads !! Although some restrictions on multi-CPU concurrency are imposed by the GIL, I understand that this can be solved in most cases. A nice feature of native threads is that waiting for a system call in one thread doesn’t block the other threads (which happens with Ruby’s green threads).
  • Excellent support for loading C/C++ DLLs. The standard ctypes module is very convenient and easy to use.

What I don’t like about Python

  • Python doesn’t have a large index of modules like Perl’s CPAN. There’s PyPi, but it appears to be both less complete and less universally used than CPAN.
  • len() is a function and not a method of string. In general, Python is less consistent in this respect than, say, Ruby. Some operations are functions (len, dir), some built-in statements (del) and some object methods (reverse, append, etc.)
  • string‘s join method is OK, but why not also add a join method to a list. It is more elegant to write [1, 2, 3].join(',') than to write ', '.join[1, 2, 3]
  • The syntax for a single-item tuple is a parsing-imposed ugliness, akin to the need to separate the closing ‘>’s in C++ nested templates.
  • Lack of the ability to use ? and ! in identifiers. I came to like the Ruby way of naming predicates and destructive functions, and with Python I’m back to the C++/Perl dilemmas of isEmpty vs. empty vs. emptyp, etc. instead of the elegant empty?
  • The unless keyword is sorely missing
  • “Batteries included” also has a downside – there are a lot of clutter and deprecated libraries. While it’s good for keeping backwards compatibility, for new users this is a bit inconvenient, because you’re not always sure what to use.
  • I’m not particularly fond of the way private methods are called in Python. while I don’t have a particular problem with methods receiving self and having to use it explicitly (it allows for some surprising flexibility, especially when passing around methods as callable objects), the syntax for private methods is too much on the fingers. Suppose your class has a private method called Foo. In C++, you’d call Foo() from another method in this class. In Python, you have to bang self.__Foo(). A bit too much. And while we’re at it, I don’t like those double underscor

Conclusion

All considered, I really like Python. The cons are mainly nits that are easy to overcome, while the pros are significant. I’ll continue practicing Python, and will write a more organized review later on.

Related posts:

  1. Qt – first impressions
  2. Python threads: communication and stopping
  3. matplotlib – plotting with Python
  4. How (not) to set a timeout on a computation in Python
  5. Python – parallelizing CPU-bound tasks with multiprocessing

26 Responses to “Python impressions”

  1. KNo Gravatar Says:

    I prefer “if not foo:” instead of “unless foo:”. Less cluttered grammar.

  2. dudeNo Gravatar Says:

    K: I love how LESS grammer seems MORE cluttered to you :P

    Plus, it has uses outside of your simple example:

    if foo != 1 and blah != True:

    can become:

    unless foo == 1 and blah:

    much sexier.

  3. markusNo Gravatar Says:

    I agree about the indent. I am a ruby guy, I never understood why indent was used against python. I agree that the indent should be in space rather than tabs, and I also agree that I think a REAL programming language should not care about indent BUT on the other hand I think the whole indent issue was blown out of proportion.

    What I however dont like about python is the implicit self. I can’t stand it.
    The “foo:” part is also annoying, I dont like to use :
    Using : there feels like using ; in perl and I think both python and ruby have got away with perl’s legacy of becoming unmaintanable.

    The CPAN thing is overrated too because I have came to realize that a lot of the cpan modules are hugely outdated.

    But since cpan keeps on coming again and again, i think it would be cool to use something that ALL THE THREE could use. And maybe include php.

    So that the scripting languages could all use a library that solves a specific issue, without the needed reimplementation in each of these language.

    I feel a lot of the repetitive task is assigned to this, and that wastes man hours…

  4. DamienNo Gravatar Says:

    For the single item tuple syntax complaint, are you suggesting that parens shouldn’t be used for tuples and only be used as a way to group expressions?

  5. monk.e.boyNo Gravatar Says:

    These are cool:

    [x for x in list]

    I don’t know what they are called, but they rock.

    Sets are also useful in all sorts of ways – it’s like having SQL inside the language that works on your data

    monk.e.boy

  6. elibenNo Gravatar Says:

    K: I find unless to be more readable than if not

    Damien: perhaps, yes. Tuples are a special syntax anyway, so why reuse the poor parens that are used for function calls and expression grouping ? Dict initialization got its syntax, so why not tuples ? Maybe tuples could also use braces.

    monk.e.boy: These are list comprehensions. The best thing in them is they’re very fast unlike high-order features in other languages.

  7. JoshNo Gravatar Says:

    Nice overview. I ended up writing a very similar post after a few weeks using Python. I did have some observations though:

    I believe that Python threads are a little bit broken. http://docs.python.org/api/threads.html. I’m not sure how much of problem it really is in real world programs.

    The syntax of join makes sense when you consider that the list you are joining shouldn’t be making assumptions about return type. There are a few different string types, and it could get messy casting your return value to the one you want.

    Also, private functions are not really private. The double underscore just a hint to the compiler to munge the name, e.g. MyClass.__func to _MyClass__func. I find that a little bit annoying, but in the scheme of things it probably doesn’t matter.

  8. TimNo Gravatar Says:

    the bit about parens is that a single-item tuple has to have a trailing comma so that there’s not confusion with unnecessary grouping parens:

    (1) == the number 1, but with extraneous parens
    (1,) == A tuple containing one element, which is the number 1

    Luckily, most places where you pass a tuple, all you need is something iterable, and [1] is a list containing only the element one.

    Oh, and if you like list comps, you gotsta love generator comps:

    x = min(item.count() for item in longlist if item.relevantTo(whatever))

  9. MaxNo Gravatar Says:

    I totally agree with you on the deprecated libraries. There are so many modules that often you search for what you need in google and start using what you find…only to find later that “library” is old and there is a “library2″ out. #python on freenode is a great resource though to find this stuff out.

  10. ThomasNo Gravatar Says:

    @Markus:

    “The CPAN thing is overrated too because I have came to realize that a lot of the cpan modules are hugely outdated.”

    I saw a presentation on “is perl dead?” or something to that effect, and they showed a chart of CPAN contributions. The rate at which CPAN contributions are made has risen steadily every year, meaning more modules are from 2007 than any other year except maybe 2008 right now. So your argument is wrong.

  11. troelsNo Gravatar Says:

    Which languages have you mainly been using before?

  12. NameNo Gravatar Says:

    “len() is a function and not a method of string. In general, Python is less consistent in this respect than, say, Ruby. Some operations are functions (len, dir), some built-in statements (del) and some object methods (reverse, append, etc.)”

    As a general rule, functions like len() will not modify their arguments, while methods may. Also, len(obj) is simply a wrapper around obj.__len__().

    del is a statement because it can’t be implemented as a function, without using horrible stack analysis hacks. As it is special in this way, it should not look like just another function.

    ————————

    “string’s join method is OK, but why not also add a join method to a list. It is more elegant to write [1, 2, 3].join(‘,’) than to write ‘, ‘.join[1, 2, 3]”

    The original rationale given is that there are only three string classes, but there are dozens or hundreds of iterable classes (not to mention user-defined). It would be annoying to force all iterable types to implement a relatively special-purpose method like join(). However, I have hope that this will be solved in Python 3 with the use of abstract base classes.

    ————————

    “The syntax for a single-item tuple is a parsing-imposed ugliness, akin to the need to separate the closing ‘>’s in C++ nested templates.”

    The tuple operator is not parens, but comma. 1, is a tuple. So is 1, 2,. The ugly special case is the empty tuple, not the 1-tuple.

    ————————

    “The unless keyword is sorely missing”

    The last thing we need is extra special cases in “if” statements. There are already three or four forms of the damn things.

  13. elibenNo Gravatar Says:

    Josh: What do you refer to when you say threads are broken ? GIL and multiprocessors, or something additional ?

    troels: I’ve added an update with a short background paragraph to provide the context.

    Name: how do you write a 1-tuple ? isn’t that ugly ?
    Whatever works for if, can work for unless too. It will make code more readable.
    Can you elaborate on abstract classes that will solve the ‘join’ issue ?

  14. ryanNo Gravatar Says:

    Some of the legacy and code strewn about is addressed in Python 3000 and the standard library cleanup. http://mail.python.org/pipermail/python-3000/2008-April/013309.html

  15. Manuel MontoyaNo Gravatar Says:

    If you use MS Windows (and let 30% of your RAM is wasted in antivirus cycles) obviously you don’t have any authority to write about good technologies.

  16. beza1e1No Gravatar Says:

    I think having ‘join’ in the str class actually makes sense. This way it works on all sequences (everything which has a __iter__ method) and you don’t have to reimplement in in lists, dicts, sets, custom collections, …

  17. Larry ClappNo Gravatar Says:

    Interesting that roughly half of what you like is the language itself, and half the community and whatnot, whereas all of what you don’t like is the language. Not bad or good, just interesting.

  18. יואבNo Gravatar Says:

    מי מכריח אותך לעבוד בוינדוס ? תעבוד בלינוקס. פייתון מותקן בברירת מחדל ברוב הפצות הלינוקס.

  19. njharmanNo Gravatar Says:

    Private methods are like curly brackets, getters setters and other OO purist cruft. You soon realize they add little but more typing. Other than __special__ methods I’ve not used private methods in a very very long time. I almost never see them in mature Python code.

    Don’t use them.

  20. ripper234No Gravatar Says:

    “len is a function and not a method”…

    Sorry to introduce on this Python discussion, but I would just like to point out that C# 3.0 have this attractive feature – it allows one to add methods to existing classes.

    Not happy with the the List class doesn’t have join()? Write
    string Join(List this, string seperator)
    {
    return seperator.Join(this);
    }

    Back to watching Babylon 5 now, and again sorry if it’s too off-topic.

  21. NameNo Gravatar Says:

    “Name: how do you write a 1-tuple ? isn’t that ugly ?
    Whatever works for if, can work for unless too. It will make code more readable.
    Can you elaborate on abstract classes that will solve the ‘join’ issue ?”

    I write all tuples wrapped in parens, because it makes it easier for the human reader to distinguish them from function parameters. I also use trailing commas, because in multi-line tuples trailing commas reduce noise in diffs. However, the computer does not care; it would be equally as happy with any style:

    a = 1,
    a = (1,)
    b = 1, 2, 3
    b = 1, 2, 3,
    b = (1, 2, 3,)

    ————————

    Abstract base classes can provide default implementations of methods. For example, the sequence ABC can simply define a method join():

    def join (self, sep):
    return sep.join (self)

    and let [1, 2, 3].join (‘,’) work the same as ‘,’.join ([1, 2, 3]).

  22. elibenNo Gravatar Says:

    Yoav: are you working ? are they using Linux at work ? If so, lucky you.

    ripper: yeah, actually Ruby has this feature too. I think it’s a bit confusing, because when you see code you thought you know its workings, it may surprise you because someone redefined the way some built-in class works.

    njharman: private methods have their uses, in Python too. And I see a lot of Python code (stdlib included) with them.

  23. Antonio OgnioNo Gravatar Says:

    This does not work:

    >>> ‘, ‘.join([1, 2, 3])
    Traceback (most recent call last):
    File “”, line 1, in
    TypeError: sequence item 0: expected string, int found

    This works:

    >>> ‘, ‘.join( ['%d' % num for num in [1, 2, 3] ] )
    ’1, 2, 3′

    In tha latter case you’re joining a list of string elements that have been created using a list comprehension thus the join works.

    Regards,

    Antonio
    Lima – Peru

  24. WilliamNo Gravatar Says:

    ‘, ‘.join(map(str, [1, 2, 3]))

    is cuter still :)

  25. Seun OsewaNo Gravatar Says:
    ", ".join(str(i) for i in [1,2,3])
  26. mikrobitti tarjousNo Gravatar Says:

    Python is just one language that I just don’t get at all. You guys seems like gurus to me :)