[:en]Tales of a GeekTrotter[:fr]Récits d'un GeekTrotter[:ja]Tales of a GeekTrotter [:en]Binary Logbook[:fr]Carnet de bord binaire[:ja]ギークの旅行

4Avr/1114

Python VS Ruby through a concrete GUI example (Qt)Python VS Ruby a travers un exemple concret (Qt)

In a previous post, I was justifying my choice to start learning Ruby instead of Python, and in another previous post I was praising QtRuby.

Now it's time to challenge QtRuby (and Ruby in general) with PyQt/PySide (and Python in general).

Due to some reasons, I had to write the same program in both languages.
Thanks to that, I better understood myself the pros and cons of both languages and will try to share my thoughts with you.

A very brief summary would be: Ruby is the winner for programming pleasure and private applications while Python is the winner when it gets serious.
For more information, read the whole post!

Dans un article précédent, je justifiait mon choix d'apprendre Ruby au lieu de Python, et dans un autre article je couvrais d'éloges QtRuby.

Maintenant il est temps de défier QtRuby (et Ruby en général) avec PyQt/PySide (et Python en général).

Suite à quelques soucis, j'ai dû écrire le même programme dans les deux langages.
Grâce à ça, j'ai mieux perçu les pour et contre des deux langages et vais tenter de partager cela avec vous.

Un résumé bref serait : Ruby est le champion pour le plaisir de programmer et pour des programmes personnels alors que Python prend la main quand on passe aux choses sérieuses.
Pour plus d'infos, lisez-donc l'article complet !

Background

I wanted to write a new GUI program, basically uploading pictures to Twitpic (the real goal is beyond that but let's make it easy).
Being a Ruby fan, I started to write my code in Ruby 1.9 with the "qtbindings" gems.
For my application, I also needed other gems like the "zip/zipfilesystem", "digest/md5" or "json".
As always with Ruby, everything went fine and after about 16 hours I had my application working.

ddl-ruby-settings

Came the moment to send the program to beta testers.
To make it simple for them, as not all of them are familiar with programming, I wanted to bundle the program in an executable with all the required DLLs, as I used to do in C++ with Qt.

Lucky me, there is something called “RubyScript2Exe” (or “AllInOneRuby”) that can do it for you!
Unlucky me, it’s not working at all! It always raises a fatal error, even with the simplest QtRuby script…
A “can’t modify frozen string (RunTimeError)” that many people seem to have and no fix available yet…

Well, there is another one called OCRA (One-Click Ruby Application Builder) that looks more serious.
It works with basic QtRuby scripts, but unfortunately not when we require some dependencies, like “zip/zipfilesystem”. It posted on their Google Groups and hopefully a solution will be found, but so far… nothing!

Update: Thanks to Lars, a simple --no-autoload made OCRA work perfectly!
So now I can easily bundle my QtRuby applications too!

That’s good to be able to quickly write programs that do their job, but it’s very frustrating when you cannot distribute them to others!
For beta testers, I could tell them to install Ruby 1.9, the required gems, Qt 4.7, and send them directly the source code, but it’s not convenient at all for them, especially if they don’t really have time to waste…

Another solution was to switch to Ruby 1.8 instead of Ruby 1.9 that I was using and hope that it works.
However, I am simply unable to run my code under Ruby 1.8 as the qtbindings are not recognized by Ruby 1.8 (even if of course I install the gem… :s).
I posted the problem on stack overflow, and, hopefully I will be able to edit this post and write that I managed to make it work on Ruby 1.8, and, with even a lot of luck, to bundle the QtRuby package with Ruby 1.8!
But for now, trying to bundle my QtRuby application just fails enormously…

So I decided to re-write the program in Python, as Python and Ruby are often compared; Python being more accepted among professionals.

Switching to Python

In Python, there are two different Qt4 bindings: PyQt4 and PySide.
They are very similar, except the licence.
PySide being supported by Nokia (which now owns Qt), I chose PySide.

Before anything else, I first tried to compile a very basic PySide program into a binary (an .exe for Windows).
There is a tool called py2exe so I tried that one and… it works very well! (Unlike RubyScript2Exe…)
I added some “import” and made some basic calls and… py2exe just worked well.

Accepted! I was ready to write my program again, but in Python!
The good thing is that I already had the “brain” of my program and new how to do it.
I just had to change the syntax from Ruby to Python, which was done quite smoothly.

It took me again about 16 hours to re-write it and add some extra features and error control.

The result is looking just exactly the same:

ddl-python-settings

The only differences are because I did not copy exactly the position and size of all the elements…

But the code is very different so let see the differences between Ruby and Python!

Similarities between Python and Ruby

First let’s talk about the similarities.

Indentation

A lot of people talk about the “whitespaces” in Python, that force you to indent your code otherwise Python does not understand it… Honestly, it is so transparent that you just forget it after a while!
I mean, when you write code in C++ or Java or whatever, you indent your code (or your IDE does it for you). It is just so natural that there is just nothing to say about it.
My Ruby code had an indentation of 4 whitespaces, and my Python code too.
I didn’t feel any difference between the two languages as for the indentation…

Object Programming

Ruby is 100% object programming, Python… is?
It seems that recent Python is also fully object oriented and I did not feel any problem with “int” or “str” that are well handled in both languages.

Libraries

For the needs of my project, both languages had the required libraries, built-in or through easy to install libraries or gems.
It is said that Python has more libraries, and I am sure it is true, but Ruby is very likely to have enough libraries to meet your needs.

Differences between Python and Ruby

Pleasure of programming

The first difference is very subjective.
I enjoy much more writing some Ruby code than Python code.
First of all, I hate the ":" in Python… I just find it too ugly (ok, very subjective…).

I also don’t like the fact that you always need to add the parentheses to the method calls to really call the method. This is also very subjective again and many people won’t agree and say it’s better to differentiate the method call and reference, but I prefer a clean code without parentheses like in Ruby (you still can add the parentheses in Ruby if you want though…).

Then, the keyword self in Python: you just have it everywhere!
All the method calls first argument are always “self”, and you always repeat “self” when you call a local class instance variable… Among 391 lines of code, I have 286 times the word “self” (more than one “self” in some lines of course), that’s too many!
I definitely prefer the @var in Ruby that some Pythonists laugh at.

Finally, the Python __init__, __str__ and other underscored methods/functions…
What the…! No, seriously, someone has to to something for that!
OK, the __str__ function that can change any object (anything?) into a string is very useful.
But please find another name…
For example __init__ and __del__ are respectively called initialize and define_finalizer in Ruby.
It’s less ugly, isn’t it?

To conclude, it’s just nicer to read and write some Ruby code than Python code (for me!).

The Blocks Power of Ruby

Here we are. The Ruby Blocks ( { } ).
They are just so convenient that once you code with it, you’re addicted!
It is possible to achieve such features in Python too, but it’s definitely better implemented in Ruby where it’s very very easy to use.

Let’s take a concrete example of a Qt signal connection.
In Python you have to pass a function as parameter of the call, even if you only have one action to do when the signal is received.

In Python, you need to define the receiving slot separately (note the “…”):

        saveSettingsButton = QtGui.QPushButton('Save Settings')
        saveSettingsButton.clicked.connect(self.saveSettings)
        ...
    @QtCore.Slot()
    def saveSettings(self):
        self.settings.setValue('author',self.author())
        QtGui.QMessageBox.information(self, 'Settings', 'Settings saved!') 

In Ruby, you can just use the blocks to write the action on-click right after:

    # Save settings
    saveSettingsButton = Qt::PushButton.new(tr('Save settings'))
    saveSettingsButton.connect(SIGNAL :clicked) {
        @settings.setValue("author", Qt::Variant.new(@author))
        Qt::MessageBox.new(Qt::MessageBox::Information, 'Settings', 'Settings saved!').exec
    }

When you will perform the action only once, or for very short actions, it is definitely better to write it directly after the connection inside the blocks, like in Ruby.

It is easier to read and maintain.

For actions to perform that can be use somewhere else in the code by another object, of course it is better to separate it, and Ruby can also do that. But for very short call, this Ruby shortcut using blocks is very convenient!

Definition of the slots/signals

In Python, you need to add a @QtCore.Slot(type1, type2,…) before the functions that you want to declare as a slot, like the previous "saveSettings(self)" method.

In Ruby, you can just add one line at the top of your class listing all the slots, like you would do in Qt/C++: slots :saveSettings, :mySlot2, :mySlot3

Same for the signals with the keyword "signals", whereas in Python you need to declare the signals at the top of your class, outside __init__

Python:

class myClass(QtCore.QObject):
    errorSignal         = QtCore.Signal(str)
    infoSignal          = QtCore.Signal(str)
    def __init__(self):
        ...
    @QtCore.Slot()
    def saveSettings(self):
        ...
    

Ruby:

class myClass < Qt::Object
  slots :saveSettings
  signals :errorSignal, :infoSignal

And finally, you can just have no slot at all in Ruby if you add your code when a signal is received inside blocks, like shown in the previous code example!

Easiness of distribution

Yes, both of them are multi-platform, and it is possible to run the same code on Windows, Linux and Mac.

However, not all the clients want to install Ruby or Python and the required libraries to run the program.

So you need to bundle the application for them, and Python is far better on this point!

I have talked about it in more details in the background introduction as it was the reason why I wrote the same program in both languages.

Refer to the first part for more details.

Performance

I was surprised reading on Internet that Ruby was performing better than Python.

I don’t know if it’s my computer or not, but for me PySide is performing far better than QtRuby…

The PySide GUI is almost immediately displayed when the program is started whereas the QtRuby GUI takes about 10 seconds to display!

The difference is even bigger when I run the debugger… Python debugger runs very smoothly whereas Ruby debugger is very slow, so slow I can’t use it… (and I used the ruby “fastdebug” :s)

My computer is not recent (an almost 3 years old AMD X2 laptop that rediscovered life when it met an SSD) so I tried on a Quad Core running Windows XP and of course the difference was smaller…

But on old machines, I beleive the PySide GUI is starting faster than the QtRuby one (and the performance of the program once launched is similar).

Conclusion

In this article, I have shown the pros and cons of Ruby and Python on my concrete example, with my subjective point of view.

Yes, I prefer Ruby as a language itself and as I already said, I enjoy much more programming in Ruby than Python.

However, in the real world, for real projects and real clients that want a bundled application that just runs smoothly, I’m afraid Python is better.

It is even far better that after justifying Ruby during all this post, I will continue further development of this program in Python… Because yes, the easiness of distribution is a crucial point in the real world and the differences between Ruby and Python are more about self-pleasure than language features.

I hope Ruby will soon become easier to bundle so I can switch back to my lovely functional blocks!

Update: Thanks to Lars, a simple --no-autoload made OCRA work perfectly!
So now I can easily bundle my QtRuby applications too!

Introduction

Je voulais écrire un nouveau programme visuel (avec GUI), qui, en gros, envoie des photos sur Twitpic (le but réel est bien plus grand que ça, mais faisons simple).
Étant un fan de Ruby, j'ai commencé à écrire mon code en Ruby 1.9 avec le gem "qtbindings".
Pour mon programme, j'avais aussi besoin d'autres gems comme "zip/zipfilesystem", "digest/md5" ou "json".
Comme toujours avec Ruby, tout se passe bien, et après une bonne douzaine d'heures de programmation, j'avais mon programme qui fonctionnait.

ddl-ruby-settings

Est venu le moment de distribuer ce programme aux bêta testeurs.
Pour leur simplifier la chose, tous n'étant pas des programmeurs, je voulais empaqueter le programme dans un exécutable avec les bibliothèques requises (DLLs ou .so), comme je faisais en Qt/C++.

Heureux moi, il y a un gem appelé "RubyScript2Exe" (ou "AllInOneRuby") qui fait ça pour vous !
Malheureux moi, ça ne marche pas du tout et aboutit toujours à une erreur fatale, même avec le plus simple des scripts QtRuby... Un joli “can’t modify frozen string (RunTimeError)” que beaucoup de personnes semblent avoir et qui n'a pas été résolu...

Enfin bon, il y en a un autre, intitulé OCRA (One-Click Ruby Application Builder) qui a l'air plus sérieux.
Effectivement, il marche avec un script QtRuby tout simple, mais malheureusement pas lorsqu'on fait appel à d'autres librairies comme “zip/zipfilesystem”. J'ai posté sur leur groupe Google et peut-être qu'un jour j'aurais une réponse, mais pour l'instant... rien !

Mise à jour : Merci à Lars, grâce à qui un simple --no-autoload a fait fonctionner OCRA parfaitement !
Donc maintenant il est possible de déployer facilement des applications QtRuby 😉

Alors c'est pas mal d'être capable d'écrire rapidement des programmes qui marchent, mais c'est très frustrant de ne pas pouvoir le partager facilement !
Pour les bêta testeurs, j'ai pu leur expliquer la démarche à suivre (installer Ruby 1.9, les gems requises, Qt 4.7, et lancer directement depuis le code source), mais ce n'est pas du tout pratique pour eux, surtout s'ils n'ont pas de temps à perdre...

Une autre solution était de passer en Ruby 1.8 et prier pour que ça marche.
Mais malheureusement sous Ruby 1.8 mon programme ne marche tout simplement pas car Ruby 1.8 ne reconnaît pas la gem "qtbindings" (qui est bien installée, bien sûr).
j'ai décri le problème sur StackOverflow, et, encore une fois, peut-être qu'un jour je pourrais éditer ce message et écrire que j'ai réussi à résoudre le problème, mais pour l'instant c'est pas gagné...
Donc pour l'instant mon programme en QtRuby échoue lamentablement lors de la phase critique de distribution...

Donc j'ai décidé de ré-écrire le programme en Python, Python et Ruby étant souvent comparés/rapprochés, Python ayant un avantage dans le monde professionnel.

Passage à Python

En Python il y a deux bindings différents de Qt4 : PyQt4 et PySide.
Ils sont très similaires (même presque entièrement compatibles), à part leur licence.
PySide étant supporté par Nokia (qui possède désormais Qt), j'ai choisi PySide.

Avant de faire quoi que ce soit d'autre, j'ai tenté d'empaqueter un programme PySide très basique dans un exécutable (un .exe sous Windows).
Pour cela, il y a un outil sympa appelé py2exe que j'ai essayé et... qui marche très bien ! (contrairement à RubyScript2Exe...).
J'ai ajouté quelques "import" et appels pour voir et... py2exe a continué de marcher 🙂

Accepté ! J'étais prêt à ré-écrire mon programme, mais en Python !

Le bon côté est que j'avais déjà le "cerveau" de mon programme et savait "comment" faire les choses.
Il ne me restait qu'à changer la syntaxe de Ruby à Python, ce qui n'a pas été si compliqué.

Cela m'a tout de même pris autant de temps que pour tout faire de 0 en Ruby, mais j'ai ajouté plus de contrôles d'erreurs et quelques nouvelles fonctionnalités.

Le résultat est juste identique :

ddl-python-settings

Les seules différences sont parce que je n'ai pas copié exactement les positions et tailles de tous les éléments...

Mais je vous assure que cette seconde prise d'écran est à partir du code 100% Python, contrairement au premier, et voyons donc les différences entre ces deux langages !

Ressemblances entre Python et Ruby

D'abord parlons des ressemblances.

Indentation

Beaucoup de gens font tout un plat des "espaces blancs" de Python, qui force le programmeur à indenter son code et qui ne s'exécute pas si c'est mal fait. Honnêtement, c'est tellement transparent qu'on oublie totalement dès les premières minutes de programmation en Python !
Quand on écrit du code en C++ ou en Java ou en quoi que ce soit, on indente notre code non ? (ou notre IDE le fait pour nous, soit)
C'est tellement naturel que je ne vois pas le souci. Mon code Ruby était indenté de 4 espaces, mon code Python aussi.
Je n'ai ressenti aucune différence sur ce point là entre les deux langages...

Programmation Objet

Ruby est 100% objet, Python… aussi ?
Il semblerait que dans ses versions récentes, Python aussi est 100% objet et je n'ai eu aucun problème à manipuler les "int" ou "str" de Python.

Bibliothèques (libraries)

Pour mon projet, j'avais besoin de plusieurs bibliothèques (libraries) comme "zip" ou "md5" ou même de l'encryptage AES, et les deux langages de programmations que sont Python et Ruby sont pourvus d'une multitude de bibliothèques, intégrées ou à installer, mais dans tous les cas facilement utilisables.
Il est dit que Python a plus de bibliothèques, et je suis sûr que c'est vrai, mais il est très probable que Ruby aussi ait une bibliothèque qui satisfasse vos besoins.

Differences entre Python et Ruby

Plaisir de programmer

Cette différence est très subjective.
J'apprécie beaucoup plus écrire des lignes de Ruby que de Python.
Tout d'abord, j'ai horreur des deux-points en Python ":",  c'est juste horrible (ok, très subjectif !).

Je n'aime pas non plus le fait de devoir toujours mettre les parenthèses lorsqu'on fait appel à une méthode/fonction. C'est aussi subjectif et beaucoup de gens ne sont pas d'accord sur ce point et justifient que c'est mieux de différencier un appel de méthode et une référence à cette méthode, mais je préfère quand même un code clean sans parenthèses de partout (comme on peut faire en Ruby par exemple, et vous pouvez toujours rajouter les parenthèses en Ruby si vous préférez :p).

Ensuite, le mot-clé self en Python : il est tout simplement PARTOUT !
Toutes les méthodes internes à une classe ont leur premier argument qui est "self", et vous répétez "self" partout dès que vous faites appel à une méthode de la classe locale ou à un attribut... Sur 391 lignes de code, j'ai 286 fois le mot "self" (en comptabilisant les fois où il y a plusieurs "self" par ligne), c'est beaucoup trop !
Je préfère largement le @var en Ruby dont se moquent certains Pythonistes.

Enfin, les __init__, __str__ et autres méthodes encadrés d'underscore...
Non mais sérieusement, il faut faire quelque chose là !
OK, la méthode __str__ qui peut changer tout objet (tout ?) en une chaîne de caractères, et qui a une implémentation par défaut pour tous les objets est utile.
Mais s'il vous plaît, trouvez lui un nom !
Par exemple __init__ et __del__ sont respectivement appelés initialize et define_finalizer en Ruby.
C'est moins moche, non ?

Pour conclure, c'est plus agréable de lire et écrire du code en Ruby (du moins pour moi !).

Le pouvoir des blocs de Ruby

Nous y sommes. Les blocs de Ruby ( { } ).
C'est tellement pratique qu'une fois que vous y avez goûté, vous êtes accro !
Il est possible d'obtenir la même fonctionnalité en Python, mais c'est sans aucun doute mieux implémenté en Ruby où c'est très facile d'utilisation.

Prenons un signal Qt en example.
En Python, vous devez passer une fonction/méthode en paramètre de l'appel (connect) même si vous n'avez qu'une seule action basique à effectuer lorsque le signal est reçu.

En Python, vous devez définir le slot qui sera exécuté lors de la réception du signal séparément (remarques les "...") :

        saveSettingsButton = QtGui.QPushButton('Save Settings')
        saveSettingsButton.clicked.connect(self.saveSettings)
        ...
    @QtCore.Slot()
    def saveSettings(self):
        self.settings.setValue('author',self.author())
        QtGui.QMessageBox.information(self, 'Settings', 'Settings saved!')

En Ruby, vous pouvez juste utiliser les blocs pour entrer le code à effectuer lors de la réception du signal juste après :

    # Save settings
    saveSettingsButton = Qt::PushButton.new(tr('Save settings'))
    saveSettingsButton.connect(SIGNAL :clicked) {
        @settings.setValue("author", Qt::Variant.new(@author))
        Qt::MessageBox.new(Qt::MessageBox::Information, 'Settings', 'Settings saved!').exec
    }

Quand l'action à effectuer n'est faite qu'une seule fois, ou si c'est une action très courte, c'est mieux de voir l'action à effectuer lors de la réception du signal directement après la définition du connect(), comme en Ruby.
Et rien ne nous empêche de faire appel à une méthode en Ruby aussi (qui revient au même qu'en Python du coup).

C'est plus facile à lire et à maintenir.

Pour des actions qui peuvent être réutilisées ailleurs par d'autre code, bien sûr qu'il vaut mieux le séparer dans une autre méthode, et rien ne nous empêche de faire appel à une méthode en Ruby aussi (qui revient au même qu'en Python du coup).
Mais pour des actions simples, les blocs de Ruby sont vraiment pratiques !

Définition des slots/signaux

En Python, vous devez ajouter @QtCore.Slot(type1, type2,…) avant les méthodes qui sont des slots, comme dans l'exemple précédent devant la méthode "saveSettings(self)".

En Ruby,  vous pouvez juste ajouter une ligne en début de classe listant les slots, un peu comme vous feriez en Qt/C++ : slots :saveSettings, :mySlot2, :mySlot3

Pareil pour les signaux avec le mot-clé "signals", alors qu'en Python vous devez déclarer les signaux en début de classe en tant que "paramètres de classe" (en dehors du __init__).

Python :

class myClass(QtCore.QObject):
    errorSignal         = QtCore.Signal(str)
    infoSignal          = QtCore.Signal(str)
    def __init__(self):
        ...
    @QtCore.Slot()
    def saveSettings(self):
        ...

Ruby :

class myClass < Qt::Object
  slots :saveSettings
  signals :errorSignal, :infoSignal

Et enfin vous pouvez simplement ne pas avoir de slots en Ruby si vous écrivez le code à exécuter lors de la réception du signal dans des blocs comme dans les exemples précédents !

Facilité de distribution

Oui, les deux langages sont multi-plateforme, et, oui, il est possible d'exécuter le même code sous Windows, Linux ou Mac.
Cependant, tous les clients n'ont pas envie d'installer Ruby ou Python et toutes les dépendances manuellement pour exécuter notre programme.

Donc il vaut mieux empaqueter son programme pour eux, et Python a une grande longueur d'avance sur ce point-là !

J'ai expliqué plus en détails ce point dans l'introduction et c'est la raison qui m'a poussée à écrire le programme dans les deux langages.
Reportez-vous à l'introduction pour plus d'infos.

Performance

J'ai été surpris en lisant sur internet que Ruby était plus performant que Python.
Je ne sais pas si c'est mon ordinateur ou pas, mais pour moi PySide est bien plus rapide que QtRuby...

Ma GUI PySide se lance presque instantanément alors que celle en QtRuby prend une dizaine de secondes avant de s'afficher !
La différence est encore plus flagrante quand je lance le débugger... Le débugger Python est très fluide, tandis que le débugger Ruby est vraiment lent, tellement lent qu'il en est presque inutilisable... (alors que j'utilise le ruby "fastdebug" :s)

Mon ordinateur n'est pas récent (presque 3 ans, un portable sous AMD X2 qui a ressuscité après réception d'un SSD), soit, mais ce n'est pas une antiquité non plus.
J'ai quand même essayé sur un Quad Core sous XP et un Core i7 sous Seven, et bien sûr la différence était bien moindre, mais toujours perceptible !

En tout cas sur des machines "normales" je pense que les GUI PySide démarrent plus vite que les GUI QtRuby (et la performance du programme en lui-même une fois lancé était identique pour ma part).

Conclusion

Dans cet article, j'ai montré les pour et contre de Ruby et Python à travers un exemple concret baisé de mon point de vue.
Oui, je préfère Ruby en tant que "langage" en lui-même et prends plus de plaisir à écrire du Ruby que du Python.

Cependant, dans le monde réel, avec des vrais projets et des clients qui veulent un programme qui fait son boulot sans avoir à installer quoi que ce soit d'autre que le programme lui-même, je crains que Python ne soit bien mieux.

C'est même tellement mieux qu'après faire les éloges de Ruby dans tout cet article, je vais continuer l'écriture de ce programme en Python... parce que oui, la facilité de distribution est un point crucial dans le monde réel, et les différences entre Ruby et Python sont plus de l'ordre de plaisir personnel que de fonctionnalités du langage.

J'espère juste que Ruby devienne plus facile à empaqueter que je puisse retourner à mes chers blocs !

Mise à jour : Merci à Lars, grâce à qui un simple --no-autoload a fait fonctionner OCRA parfaitement !
Donc maintenant il est possible de déployer facilement des applications QtRuby 😉