Récits d'un GeekTrotter Carnet de bord binaire

4avril/1114

Python VS Ruby a travers un exemple concret (Qt)

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 !

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 😉
  • Pingback: the truth about six pack abs review()

  • Pingback: penis advantage()

  • Pingback: Cody Shappell()

  • Pingback: Honey Curson()

  • Pingback: Thalia Vogds()

  • Pingback: Thalia Vogds()

  • Marvin Roger

    En Python, il est également possible de faire des fonctions anonymes (les « blocs » de Ruby). Il s’agit des fonctions lambda, un petit exemple :

    ####Python

    def executer(fonction):
    fonction()

    executer(lambda: (
    print(‘1ère instruction’),
    print(‘2ème instruction’),
    print(‘…’)
    ))
    ####

    C’est un peu particulier comme syntaxe, mais le fait est que ça existe.

    • ThomasDalla

      Effectivement, c’est possible, bien que la syntaxe soit quelque peu moins commode.
      Merci de le préciser.

  • Phil Howell

    This is a great write up! I personally come from a C++/C# background and I’ve been working with Python and Ruby for quite a while now (mainly for some of my customers who require rapidly built Amazon API based apps who don’t have the time or knowledge to get into the .NET languages). I’m in agreement with you though, I personally enjoy writing code in Ruby more – I find it flows more naturally. That said (aside from the syntactic nastiness that __init__ etc) I find Python to be a breeze, and it’s extremely readable for non-programmers too – who are largely my target audience at the moment (the rise of the Dev-Ops is a blessing a curse sometimes). It’s good to see that the conclusion you were drawing 3 years ago hold true in 2014.

  • jdd

    Qu’en est-il en 2015 ? Est-il toujours aussi difficile de distribuer du QtRuby ?

    • ThomasDalla

      Très bonne question. Je ne programme ni en Python ni en Ruby depuis plusieurs années donc à vous de mettre cet article à jour 🙂