From 36ead19ba65ec69b2d2a1e8d93fba6796b022f77 Mon Sep 17 00:00:00 2001 From: Etienne Trimaille Date: Mon, 25 Nov 2024 08:57:02 +0100 Subject: [PATCH] Update memo Python --- docs/fonctions-scripts.md | 16 ++++-- docs/memo-python.md | 83 +++++++++++++++++------------- docs/selection-parcours-entites.md | 67 ++++++++++++++++++++---- mkdocs.yml | 4 +- 4 files changed, 116 insertions(+), 54 deletions(-) diff --git a/docs/fonctions-scripts.md b/docs/fonctions-scripts.md index bc1430c..a106c9f 100644 --- a/docs/fonctions-scripts.md +++ b/docs/fonctions-scripts.md @@ -365,12 +365,14 @@ with edit(layer): Nous allons avoir besoin de plusieurs classes dans l'API QGIS : * `QgsProject` : [PyQGIS](https://qgis.org/pyqgis/master/core/QgsProject.html) / [CPP](https://api.qgis.org/api/classQgsProject.html) +* `QgsVectorLayer` : [PyQGIS](https://qgis.org/pyqgis/master/core/QgsVectorLayer.html) / [CPP](https://api.qgis.org/api/classQgsVectorLayer.html) * Enregistrer un fichier avec `QgsVectorFileWriter` : [PyQGIS](https://qgis.org/pyqgis/master/core/QgsVectorFileWriter.html) / [CPP](https://api.qgis.org/api/classQgsVectorFileWriter.html) -* Un champ : `QgsField` ([PyQGIS](https://qgis.org/pyqgis/master/core/QgsField.html) / [CPP](https://api.qgis.org/api/classQgsField.html)), +* Un champ dans une couche vecteur : `QgsField` ([PyQGIS](https://qgis.org/pyqgis/master/core/QgsField.html) / [CPP](https://api.qgis.org/api/classQgsField.html)), attention à ne pas confondre avec `QgsFields` ([PyQGIS](https://qgis.org/pyqgis/master/core/QgsFields.html) / [CPP](https://api.qgis.org/api/classQgsFields.html)) qui lui représente un ensemble de champs. * Une entité `QgsFeature` [PyQGIS](https://qgis.org/pyqgis/master/core/QgsFeature.html) / [CPP](https://api.qgis.org/api/classQgsFeature.html) -* Pour le type de géométrie : Utiliser `QgsVectorLayer::geometryType()` et également la méthode `QgsWkbTypes::geometryDisplayString()` pour sa conversion en chaîne "lisible" +* Pour le type de géométrie : Utiliser `QgsVectorLayer` `geometryType()` et également la méthode `QgsWkbTypes.geometryDisplayString()` pour sa conversion en chaîne "lisible" + * [PyQGIS](https://qgis.org/pyqgis/master/core/QgsWkbTypes.html) / [CPP](https://api.qgis.org/api/classQgsWkbTypes.html) Pour le type de champ, on va avoir besoin de l'API Qt également : @@ -396,8 +398,14 @@ Il va y avoir plusieurs étapes dans ce script : 1. Enregistrer en CSV la couche mémoire !!! tip - Pour déboguer, on peut afficher la couche mémoire en question avec `QgsProject.instance().addMapLayer()` - + Pour déboguer, on peut afficher la couche mémoire en question avec `QgsProject.instance().addMapLayer(layer_info)` + + +# QgsvectorLayer pyqgis ne cntient pas Addfeature + +AJouter indice QgsWkbTypês +V4 fileName + ### Solution ```python diff --git a/docs/memo-python.md b/docs/memo-python.md index 33863aa..b3e4750 100644 --- a/docs/memo-python.md +++ b/docs/memo-python.md @@ -11,14 +11,16 @@ * Grosse communauté * De nombreux packages disponibles sur internet sur [PyPi.org](https://pypi.org/) +Exemple d'un code qui déclare une variable et compare si sa valeur est supérieur à 5 afin d'afficher un message : + ```python # Déclaration d'une variable de type entier -x = 5 +x = 10 # Déclaration d'une variable chaîne de caractère -info = 'X est compris entre 0 et 10' +info = 'X est supérieur à 5' -if 0 < x < 10: +if x > 5: print(info) ``` @@ -40,6 +42,7 @@ if 0 < x < 10: * Python 3.7 minimum for QGIS 3.20 * Python 3.9 minimum for QGIS 3.40 * Version de Python souvent supérieure à la version minimum, sauf sur MacOS… 😑 + * [Python release cycle](https://devguide.python.org/versions/#versions) ## Rappel de base sur Python @@ -67,29 +70,36 @@ objet... Il y a un faible typage des variables, c'est-à-dire qu'une variable peut changer de type au cours de l'exécution du programme. +```python +# Pour créer une variable, on déclare juste le nom de la variable ainsi que sa valeur : +compteur = 0 +``` + +Nous allons par la suite utiliser `type(variable` pour vérifier le **type** de la variable. + ```python mon_compteur = 0 type(mon_compteur) -mon_compteur = False -type(mon_compteur) +est_valide = False +type(est_valide) -mon_compteur = 'oui' -type(mon_compteur) +nom_couche = 'oui' +type(nom_couche) -mon_compteur = "non" -type(mon_compteur) +nom_couche = "non" +type(nom_couche) -mon_compteur = 3.5 -type(mon_compteur) +densite = 3.5 +type(densite) -mon_compteur = None -type(mon_compteur) +unknown = None +type(unknown) ``` @@ -102,21 +112,26 @@ Il existe quatre types de structure de données : * les listes (modifiables) ```python +# Créer une liste vide nombres = [] type(nombres) -nombres.append(1) -nombres.extend([2, 3, 4]) -nombres -[1, 2, 3, 4] +# Créer une liste avec des éléments à l'intérieur +mois = ['janvier', 'février', 'mars'] + +# Ajouter un élément +mois.append('avril') +# Ajouter une autre liste +mois.extend(['mai', 'juin']) +# Nombre de mois +len(mois) -# Autre exemple -mois = ['janvier', 'février', 'mars', 'avril'] +# On peut accéder à un élément avec un "index" à l'aide de [] mois[2] -mars +# Attention à l'index maximum mois[12] Traceback (most recent call last): File "/usr/lib/python3.7/code.py", line 90, in runcode @@ -128,18 +143,13 @@ IndexError: tuple index out of range * les tuples (non modifiables) ```python -liste_vide = () -liste = (1 , 2, 3, 'bonjour') +liste = ('oui', 'non') type(liste) len(liste) -4 +2 liste[0] -1 -liste[0:2] -(1, 2) -liste[2:] -(3, 'bonjour') + liste[5] Traceback (most recent call last): File "/usr/lib/python3.7/code.py", line 90, in runcode @@ -152,18 +162,17 @@ IndexError: tuple index out of range * les dictionnaires *Attention*, les dictionnaires ne sont pas ordonnés, de façon native, même depuis Python 3.9. -Si vraiment, il y a besoin, il existe une classe `OrderedDict`, mais ce n'est pas une structure de données -native dans Python. -C'est un objet. +Si vraiment, il y a besoin, il existe une classe [OrderedDict](https://docs.python.org/3/library/collections.html), +mais ce n'est pas une structure de données native dans Python. +C'est un objet qu'il faut importer. ```python -personne = {} -type(personne) +commune = {} +type(commune) # -personne['prenom'] = 'etienne' -personne['nom'] = 'trimaille' -personne['est_majeur'] = True -personne['age'] = 35 +commune['nom'] = 'Besançon' +commune['code_insee'] = 25056 +commune['est_prefecture'] = True ``` ## Les commentaires diff --git a/docs/selection-parcours-entites.md b/docs/selection-parcours-entites.md index f5a5449..71b8be1 100644 --- a/docs/selection-parcours-entites.md +++ b/docs/selection-parcours-entites.md @@ -41,7 +41,7 @@ layer.invertSelection() layer.removeSelection() ``` -Le raccourci `iface.activeLayer()` est très pratique, mais de temps en temps on a besoin de **plusieurs** couches qui +Le raccourci `iface.activeLayer()` est très pratique, mais de temps en temps, on a besoin de **plusieurs** couches qui sont déjà dans la légende. Il existe dans `QgsProject` plusieurs méthodes pour récupérer des couches dans la légende : ```python @@ -217,7 +217,7 @@ Dans le langage informatique, une exception peut-être : * levée ("raise" en anglais) pour déclencher une erreur * attrapée ("catch" en anglais, ou plutôt "except" en Python) pour traiter l'erreur -Essayons dans la console de faire une opération 10 / 2 : +Essayons dans la **console** de faire une opération 10 / 2 : ```python 10 / 2 @@ -228,31 +228,76 @@ Essayons cette fois-ci 10 / 0, ce qui est mathématiquement impossible : 10 / 0 ``` +Passons cette fois-ci dans un **script** pour que cela soit plus simple, et voir que le script s'arrête brutalement 😉 + +```python +print('Début') +print(10 / 0) +print('Fin') +``` + On peut "attraper" cette erreur Python à l'aide d'un `try ... except...` : ```python +print('Début') try: - 10 / 2 + print(10 / 2) except ZeroDivisionError: print('Ceci est une division par zéro !') +print('Fin') ``` Le `try` permet d'essayer le code qui suit. Le `except` permet d'attraper en filtrant s'il y a des exceptions et de traiter l'erreur si besoin. +!!! tip + On peut avoir une ou plusieurs lignes de code dans chacun de ces blocs. On peut appeler des fonctions, etc. + +### Une exception remonte le fil d'exécution du programme + +**Important**, une exception **remonte** tant qu'elle n'est pas **attrapée** : + +```python +def function_3(): + print("Début fonction 3") + a = 10 + b = 0 + print(f"→ {a} / {b} = {a/b}") + print("Fin fonction 3") + +def function_2(): + print("Début fonction 2") + function_3() + print("Fin fonction 2") + +def function_1(): + print("Début fonction 1") + function_2() + print("Fin fonction 1") + +function_1() +``` + +On voit que Python, quand il peut, nous indique la "stacktrace" ou encore "traceback", +c'est-à-dire une sorte de fil d'ariane. + +### Héritage des exceptions + Toutes les exceptions héritent de `Exception` donc le code ci-dessous fonctionne, mais n'est pas recommandé, car il masque d'autres erreurs : ```python try: - 10 / 2 + print(10 / 2) except Exception: print('Erreur inconnue') ``` +On peut par contre "enchaîner" les exceptions, afin de filtrer progressivement les exceptions. + ```python try: - 10 / 0 + print(10 / 0) except ZeroDivisionError: print('Erreur, division par 0') except Exception: @@ -267,9 +312,9 @@ dans la `QgsMessageBar` de QGIS, sans tenir compte de la division par zéro : ```python def diviser(a: int, b: int): - """ Divise 2 nombres et affiche le résultat dans la message bar de QGIS. """ + """ Divise 2 nombres et affiche le résultat dans la "message bar" de QGIS. """ result = a / b - iface.messageBar().pushMessage('Résulat', f'{a} / {b} = {result}', Qgis.Success) + iface.messageBar().pushMessage('Résultat', f'{a} / {b} = {result}', Qgis.Success) diviser(10, 0) ``` @@ -277,13 +322,13 @@ diviser(10, 0) En tenant compte d'une possible erreur lors de l'opération mathématique : ```python -def diviser(a, b): +def diviser(a: int, b: int): try: result = a / b except ZeroDivisionError: iface.messageBar().pushMessage('Division par 0', f'{a} / {b} est impossible', Qgis.Warning) else: - iface.messageBar().pushMessage('Résulat', f'{a} / {b} = {result}', Qgis.Success) + iface.messageBar().pushMessage('Résultat', f'{a} / {b} = {result}', Qgis.Success) diviser(10, 2) ``` @@ -306,7 +351,7 @@ Correction possible de l'exercice : ```python layer = iface.activeLayer() request = QgsFeatureRequest() -# request.setFilterExpression('to_int( "POPUL" ) < 1000') +# request.setLimit(5) # Pour aller plus vite si-besoin request.addOrderBy('NOM') request.setSubsetOfAttributes(['NOM', 'POPUL'], layer.fields()) for feature in layer.getFeatures(request): @@ -333,7 +378,7 @@ if 'densite' not in layer.fields().names(): index = layer.fields().indexFromName('densite') layer.startEditing() request = QgsFeatureRequest() -# request.setFilterExpression('to_int( "POPUL" ) > 10000') +# request.setLimit(5) # Pour aller plus vite si-besoin request.addOrderBy('NOM_COM') request.setSubsetOfAttributes(['NOM_COM', 'POPUL'], layer.fields()) for feature in layer.getFeatures(request): diff --git a/mkdocs.yml b/mkdocs.yml index 213781f..3bf3969 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,8 +21,8 @@ nav: - Sélection & Parcours: selection-parcours-entites.md - Utilisation simple: - Action: action.md - - Formulaire: formulaire.md - Expression: expression.md + - Formulaire: formulaire.md - Utilisation avancé: - Script Processing: script-processing.md - Extension Générique: extension-generique.md @@ -31,10 +31,10 @@ nav: - Application standalone: standalone.md - Sujet PyGIS thématique: - PostGIS: postgis.md + - Migration majeur: migration-majeure.md - Outils annexes: - Déploiement d'une extension: extension-deploiement.md - IDE & Git: ide-git.md - - Migration majeur: migration-majeure.md plugins: - search