PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Comment installer des libs Python externes dans QGIS ? 5

mardi 31 mars 2015 à 11:16

QGIS vient avec son Python perso, séparé du reste du système. Du coup si vous faites un pip install une_lib, elle s’installera sur le Python du système, et non de QGIS. Si vous voulez utiliser requests ou arrow pour vos scripts, c’est relou, il faut les télécharger, les extraire, et les mettre à la main sur le PYTHONPATH.

Il est pourtant possible de manipuler interpréteur Python de QGIS, c’est juste que ça saute pas aux yeux :) Ca va nous permettre d’installer pip (si ça ne vous parle par, on a un article pour ça), à la mano, à l’ancienne, comme un homme qui fait ses cartes à la main sur des calques avec des lames de rasoirs en guise de gomme.

D’abord, trouver où il est niché. Sur ma machine, il est dans “C:\Program Files\QGIS Wien\bin\python.exe”.

Si vous le démarrer, vous noterez qu’il plante en prétextant qu’il ne trouve pas le module site. Il faut donc lui dire où sont installées les libs en faisant:

set PYTHONHOME=chemin vers l'installation de Python de QGIS.

Chez moi ça donne :

set PYTHONHOME=C:\Program Files\QGIS Wien\apps\Python27\

Ainsi vous pouvez lancer C:\Program Files\QGIS Wien\bin\python.exe dans la console et obtenir le shell Python de QGIS.

Ensuite, il faut télécharger ez_tools.py qui va nous servir à installer setuptools dans le Python voulu. C’est un script Python (meta meta ! \^O^\), qu’on lance avec l’interpréteur qui nous intéresse, dans mon cas:

"C:\Program Files\QGIS Wien\bin\python.exe" C:\Users\sam\Downloads\ez_setup.py

Il va télécharger depuis internet tout un tas de trucs, et installer tout un tas de machins. Parfois, ça va râler parce que vous n’avez pas les droits. Dans ce cas, lancez la console en mode admin, et faites un cd sur le bureau avant de lancer la commande.

Bien, on a setuptools, qui a du rajouter la commande easy_install. Chez moi elle est dans “C:\Program Files\QGIS Wien\apps\Python27\Scripts”.

On va s’en servir pour installer pip.

Que de bordel !

C:\Program Files\QGIS Wien\apps\Python27\Scripts\easy_install.exe pip

Ca remouline, ça re-dl.

Ce qui va nous mettre la comamnde pip dans le même dossier qu’easy_install.

Et pouf, en utilisant le chemin canonique, on peut utiliser pip :

C:\Program Files\QGIS Wien\apps\Python27\Scripts\pip install ipython pyrealine

Faites attention néanmoins, utiliser cet interpréteur hors de QGIS ne vous donne pas accès à des objets comme iface qui n’existent que quand l’UI de QGIS est chargée. Ca reste pratique pour installer des libs externes, faire des tests python rapides dans le même environnement que QGIS sans lancer tout le bouzin et se la péter face à ses collègues géomaticiens qui ont l’air d’une poule avec un canif devant une console.

Vrac Python (encore) 5

lundi 30 mars 2015 à 21:01

Les billets en vrac sont au blogging ce que les pâtes de fond de frigo sont à la cuisine: le dernier recours en cas de manque de ressources ou d’inspiration.

Mais bizarrement, c’est aussi quelque chose qui plait beaucoup. C’est d’ailleurs les posts que je préférais sur le standblog, ou les trucs qui énervaient sebsauvage avant qu’il shaarlise tout ça.

Bref, quelques trucs en Python qui peuvent être passés sous le radar.

Min et Max ont un param “key”

Si vous avez lu l’article sur le tri, vous savez que sorted() et sort() peuvent prendre une fonction de callback via l’argument key, permettant de choisir comment extraire l’information qui va servir à déterminer la place de chaque élément.

min et max marchent de la même manière :

>>> scores = {"allemagne": 1,"montagne": 0}
>>> scores.items()
dict_items([('allemagne', 1), ('montagne', 0)])
>>> max(scores.items())
('montagne', 0)
>>> max(scores.items(), key=lambda x: x[1])
('allemagne', 1)

next possède un second paramètre

Vous savez, sur les dicos, on peut choper une valeur par défaut si une clé n’existe pas :

>>> scores.get("allemagne")
1
>>> scores.get("kamoulox", 1j)
1j

Mais ceci n’existe pas pour les listes. Je pensais que c’était un oubli, mais en fait c’est comblé par le deuxième argument de next :

>>> l = list(range(1))
>>> l
[0]
>>> g = iter(l)
>>> next(g, 1j)
0
>>> next(g, 1j)
1j

__future__ n’est pas bullet proof

Certains comportements backportés en Python 2.7 ne peuvent pas être parfaitement implémentés. Ce sont “ce qu’on peut avoir de plus proche”. C’est déjà pas mal, et je les active tout le temps, mais ça peut vous rattraper à un moment inattendu.

Par exemple, les caractères d’échappements unicodes.

En Python 3:

>>> r"\u"
'\\u'

En Python 2.7, par contre, utiliser u et r entraine une exception :

>>> ur"\u"
  File "<stdin>", line 1
SyntaxError: (unicode error) 'rawunicodeescape' codec can't decode bytes in position 0-1: truncated \uXXXX

Et donc si on active les littéraux unicodes (ce qui est une bonne pratique) :

>>> r"\u"
  File "<stdin>", line 1
SyntaxError: (unicode error) 'rawunicodeescape' codec can't decode bytes in position 0-1: truncated \uXXXX

De quoi se gratter la tête sur des regexs ou des chemins de fichiers Windows.

Reportez, il en restera toujours quelque chose 4

samedi 28 mars 2015 à 10:54

L’année dernière j’ai reporté un bug sur nuitka, un compilateur Python.

Mon rapport était incomplet, et on m’a demandé plus d’informations. J’ai pris du temps, mais j’ai répondu.

Malheureusement à la deuxième demande d’informations, j’ai trainé la patte. Je l’ai mis dans ma todo list, et ai regardé en chien de fusil l’entrée pendant des mois.

Il fallait réinstaller nuitka, me replonger dans ce que je voulais faire à l’époque, lancer la compilation, répondre sur le bug tracker. Une heure dépensée au moins.

C’est ça dont les gens ne se rendent pas compte : participer à l’open source, ou à un quelconque effort collectif, ça prend énormément de temps. Entre les interruptions que ça génère, l’historique qu’il faut remonter à chaque fois, le changement de contexte, et bien entendu l’activité elle-même.

Donc, quand on vous rapporte un bug, même de manière énervée, bénissez le Dieu de la procrastination que la personne ait pris le temps de le faire. Vous ne vous devez rien l’un à l’autre, et c’est un beau moment de synergie humaine.

En l’occurrence, donc, mon interlocuteur a fini par marquer le bug comme “lack of feedback”.

D’un certain côté, ça m’a soulagé, je n’avais plus du tout envie de m’y remettre.

D’un autre côté, j’ai culpabilisé, me disant que quand même, j’utilise du FOSS, faudrait contribuer plus, et que j’avais fais perdre du temps à l’auteur.

Au final, cette semaine, je reçois une notification comme quoi une autre personne a continué le fil : il a le même problème.

J’avais oublié ce détail : en prenant le temps d’ouvrir un fil de discussion, je lui avais donné vie. Je lui avait donné une légitimité. Et il se trouve qu’avec les infos fournies par le nouvel arrivant, le ticket a été marqué en bug, et sera corrigé.

C’est un effort collectif, et même si je n’ai pas fais autant que ce que j’aurais voulu, j’ai joué mon rôle. Un rôle imparfait, mais utile.

Donc souvenez-vous : reportez vos bugs, il en restera toujours quelque chose.

Views VS generators 6

mercredi 25 mars 2015 à 19:57

Avec Python 2.7, un outil appelé les “views” (les “vues”) est apparu. Une vue est juste un enrobage qui permet de voir un objet d’une certaine façon, et de le manipuler d’une certaine façon (avec une autre API), sans changer cet objet.

Les vues ont surtout été notables pour leur apparition dans les dictionnaires avec Python 2.7:

    >>> scores = {"sam": 1, "max": 0}
    >>> scores.items() # retourne une lsite
    [('max', 0), ('sam', 1)]
    >>> scores.iteritems() # retourne un générateur
    <dictionary-itemiterator object at 0x7f8782a26628>
    >>> list(scores.iteritems()) # sans views
    [('max', 0), ('sam', 1)]
    >>> scores.viewsitems() # avec views
    Traceback (most recent call last):
      File "<ipython-input-12-dc0b08011047>", line 1, in <module>
        scores.viewsitems() # avec views
    AttributeError: 'dict' object has no attribute 'viewsitems'
 
    >>> scores.viewitems() # retourne une vue
    dict_items([('max', 0), ('sam', 1)])

Néanmoins personne ne les a vraiment utilisé, et c’est un tort. Elles sont en effet très performantes, et pour cette raison sont retournées par défaut avec items() en Python 3.

En effet, les vues ne sont qu’un enrobage : elles ne contiennent rien, et donc ne prennent pas beaucoup mémoire, tout comme les générateurs.

Mais contrairement aux générateurs, les vues ne se vident pas et peuvent exposer une API plus complète que les générateurs, comme par exemple déclarer une taille :

>>> items = scores.iteritems()
>>> list(items)
[('max', 0), ('sam', 1)]
>>> list(items) # woops
[]
>>> items = scores.viewitems()
>>> list(items)
[('max', 0), ('sam', 1)]
>>> list(items)
[('max', 0), ('sam', 1)]
>>> len(scores.iteritems()) # nope
Traceback (most recent call last):
  File "<ipython-input-21-9c7f250da51d>", line 1, in <module>
    len(scores.iteritems())
TypeError: object of type 'dictionary-itemiterator' has no len()
 
>>> len(scores.viewitems())
2

Alors certes, on ne peut pas mettre des vues partout, et les générateurs restent utiles. Mais quand il est possible de les utiliser, et à moins d’avoir besoin d’une liste afin de modifier les valeurs in place, il n’y pas de raison de ne pas le faire : c’est le meilleur des deux mondes.

Tableau de référence des exceptions en Python 6

lundi 23 mars 2015 à 00:23

J’ai fais un topo sur la gestion des erreurs en python, mais je pense que les débutants peuvent bénéficier d’un petit tableau pour s’y retrouver quand ils tombent sur les erreurs les plus courantes.

Les exceptions suivantes sont levées en cas d’erreur. Elles héritent toutes de StandardError :

Exception Cause Résolution
NotImplementedError Un développeur a volontairement levé cette exception dans une des méthodes de sa classe afin de signaler que c’est aux enfants de la classe de l’implémenter. N’utilisez pas la classe directement, mais créez une classe enfant. Overridez la méthodes afin de lui donner un comportement. Si vous ne comprenez pas ce que je viens de dire, lisez le guide sur la POO.
IndentationError ou TabError Le fichier mélange des tabs et des espaces ou n’utilisent pas le même nombre de tabs ou d’espaces partout. Activez l’affichage des tabs et espaces dans votre éditeur de texte, et assurez-vous d’utiliser 4 espaces partout comme valeur d’indentation.
ImportError Python ne peut pas importer un module. Vérifier que le nom du module ne comporte pas de fautes (Python est sensible à la casse). Assurez-vous que le module est importable (situé dans un dossier du PYTHON PATH) et qu’il ne contient pas d’erreurs empêchant son importation, telle qu’une référence cyclique. Si vous ne savez pas ce qu’est le PYTHON PATH, lisez l’article sur les imports.
AssertionError Une expression assert est fausse. Si c’est dans votre code, retirez le assert, ce mot clé n’est que pour les tests unittaires. Si vous avez la malchance de tomber sur une lib qui l’utilise comme garde fou pour le passage d’arguments valeur, lisez le code source, et passez une valeur qui rendra l’expression vraie. Si vous en avez dans les tests et que vous ne savez pas quoi en faire, lisez le guide sur le tests.
AttributeError Vous demandez à un objet de vous fournir un attribut qu’il ne possède pas. Vérifiez que le nom de l’attribut et le nom de l’objet ne contiennent pas de faute (Python est sensible à la casse). Vérifier que l’attribut a bien été créé avant son accès et pas supprimé entre temps (Python est dynamique, les attributs peuvent être créés et supprimés à la volée. Si le cas où l’attribut n’existe pas est un cas valide, vous pouvez tester cette existence avec hasattr() ou obtenir une valeur par défaut avec getattr().
NameError Vous tentez d’utiliser un nom (de variable, fonction, classe, etc) qui n’existe pas. Vérifiez que ce nom ne contient pas de faute (Python est sensible à la casse). Assurez-vous que ce que vous nommez a bien été créé avant cette ligne.
IndexError Vous tentez d’accéder à une partie d’une indexable (souvent une liste ou un tuple) qui n’existe pas. Assurez vous que l’indexable contient assez d’éléments. Si le cas d’un indexable trop court est normal, vous pouvez vérifier la longueur de l’indexable avec len(), ou utilisez un try/except.
KeyError Vous tentez d’accéder à une clé d’un mapping (souvent un dictionnaire) qui n’existe pas. Assurez vous que la clé existe. Si le cas d’une clé inexistante est normal, vous pouvez vérifier qu’une clé est dans le mapping avec ‘in’, utiliser try/except ou obtenir une valeur par défaut avec get(). Dans le cas où vous souhaitez aussi que la valeur par défaut soit ajoutée à la collection, utilisez setdefault() ou collection.defaultdict().
TypeError Vous tentez une opération incompatible avec ce type. Si l’erreur a lieu au niveau d’une fonction que vous appelez, assurez-vous de passer des paramètres du type attendu par la fonction. Vous pouvez vérifier le type d’un objet avec type(). Vérifiez également que vous n’utilisez pas un opérateur incompatible avec un type (par exemple, & ne fonctionne pas sur les strings) ou entre deux types incompatibles (par exemple, il n’est pas possible d’additionner une string avec un entier).
ValueError Vous passez une valeur à une fonction qui n’a aucun sens dans ce contexte ou dont le sens est ambiguë. Assurez-vous de ne pas passer une valeur aberrante et que le résultat attendu soit évident. Par exemple, si vous essayez de faire int(‘é’), la conversion de la lettre “é” en entier n’a pas de résultat évident.
UnicodeDecodeError Vous gérez votre texte comme un porc. Lisez le guide sur l’encoding.
UnicodeEncodeError
OverflowError Vous faites des calculs trop gros pour les types numériques des base Utilisez le module decimal
ZeroDivisionError Vous faites une division par zéro Assurez-vous que sous aucune condition aucun dénominateur n’est égal à 0
IOError Erreur d’entrée / sortie Vérifiez que vous pouvez lire / écrire depuis et vers la ressource que vous utilisez. Parmi les problèmes récurrents : disque dur plein, système corrompu, absence de permissions, fichier inexistant, etc.
OSError L’OS retourne une erreur Les causes peuvent être très variées, mais concernent souvent le système de fichier ou l’utilisation d’un sous-process. Vérifier les lignes où vous utiliser les modules os, shutils, popen, subprocess, multiprocessing, etc.
MemoryError Vous utilisez trop de mémoire Vérifiez vos remplissages de listes et dictionnaires, particulièrement si vous en déclarez un comme valeur par défaut d’un paramètre.

Je n’ai pas mentionné quelques exceptions beaucoup plus rares, mais vous pouvez trouver la liste complète ici.

Toutes les exceptions héritent de BaseException, y compris Exception.

Il existe 3 exceptions qui n’héritent pas de Exception, et ne représentent PAS des erreurs :

Bien qu’héritant d’Exception, les warnings sont des mécanismes un peu particulier. Ils sont tous notés XxxWarning (ex: DeprecationWarning pour signaler l’usage d’une fonctionnalité en cours de dépréciation) et sont des enfants de Warning.

Généralement les warnings ne sont pas fait pour être levés avec raise, mais appelé avec warnings.warn. Par défaut ces warnings sont affichés, mais peuvent être réduits au silence, filtrés ou levés comme exception selon le désir du développeur.

Enfin, StopIteration est levée quand on appelle next() sur un itérateur vide. C’est le seul enfant de Exception (avec Warning) qui n’hérite pas de StandardError car ce n’est pas une erreur en soit, mais simplement un mécanisme de contrôle de flux qui dit à la boucle for quand s’arrêter.