Ecriture de messages dans les outils de script
Dans vos scripts, vous avez besoin d'importer ArcPy, comme suit :
import arcpy
Lorsque le script est exécuté en tant qu'outil de script, Arcpy est pleinement conscient de l'application à partir de laquelle il est appelé (ArcMap ou ArcCatalog, par exemple). Il s'agit en fait d'utiliser les mêmes objets que l'application utilise, plutôt que de créer de nouveaux objets. Tous les paramètres d'environnement définis dans l'application, tels que overwriteOutput et scratchWorkspace, sont disponibles. Vous pouvez alors écrire des messages avec ArcPy et ceux-ci s'affichent automatiquement dans la boîte de dialogue de progression, le résultat de l'outil et la fenêtre Python. Cela signifie également que tout outil de modèle ou de script qui appelle votre outil de script a accès aux messages que vous écrivez.
Si l'on compare avec un script autonome, votre script n'est pas appelé à partir d'une application ArcGIS, il n'y a donc pas de boîte de dialogue de progression, de résultat ni de fenêtre Python où vos messages peuvent être affichés. Si vous appelez un autre script (autonome) de l'intérieur de votre script, le script appelé (s'il utilise des outils de géotraitement) est complètement distinct de celui que vous avez créé, et les messages ne sont pas partagés entre-eux. Le partage des messages et des environnements constitue l'un des principaux avantages des outils de script.
Voici les quatre fonctions ArcPy d'écriture de messages :
- AddMessage("message") — pour les messages d'information générale (gravité = 0) ;
- AddWarning("message") — pour les avertissements (gravité = 1) ;
- AddError("message") — pour les erreurs (gravité = 2) ;
- AddIDMessage(MessageType, MessageID, AddArgument1, AddArgument2) — utilisé pour les erreurs et les avertissements (l'argument MessageType détermine la gravité). Un appel de la méthode AddIDMessage() affiche un message court, ainsi que l'ID du message, qui fournit un lien vers une explication de la cause et des solutions au problème. Lorsque vous ajoutez un message d'erreur (en utilisant AddError() ou AddIDMessage(), les éléments suivants se produisent :
- Votre script poursuit son exécution. Il vous appartient d'ajouter la logique de gestion des erreurs appropriée et d'interrompre l'exécution de votre script. Par exemple, il se peut que vous deviez supprimer des fichiers intermédiaires ou des curseurs.
- Lors du renvoi de votre script, le modèle ou le script qui appelle reçoit une erreur système et l'exécution s'interrompt.
Exemple d'ajout de messages
L'exemple suivant copie une liste de classes d'entités d'un espace de travail à un autre. Une conversion automatique est effectuée si les espaces de travail sont différents, par exemple une géodatabase vers un dossier. Un système de gestion des erreurs est utilisé pour identifier les éventuels problèmes et messages renvoyés ; sinon, des messages d'informations d'opération réussie sont renvoyés lors de l'exécution. Cet exemple utilise la directive from <module> import. Dans ce cas, le module est ScriptUtils, dont le code est fourni ci-dessous. Ce code utilise également les blocs try/except.
Pour en savoir plus sur les blocs try/except
# ConvertFeatures.py # Converts feature classes by copying them from one workspace to another # # Import the ScriptUtils utility module which, in turn, imports the # standard library modules and imports arcpy # from ScriptUtils import * # Get the list of feature classes to be copied to the output workspace # in_feature_classes = arcpy.GetParameterAsText(0) # Establish an array of input feature classes using the ScriptUtils routine. # in_feature_classes = SplitMulti(in_feature_classes) # Get the output workspace # out_folder = arcpy.GetParameterAsText(1) # Loop through the array copying each feature class to the output workspace # for in_feature_class in in_feature_classes: try: # Create the output name # feature_class_name = arcpy.ValidateTableName(in_feature_class, out_folder) # Add an output message # arcpy.AddMessage("Converting: " + in_feature_class + " To " +\ feature_class_name + ".shp") # Copy the feature class to the output workspace using the ScriptUtils routine # CopyFeatures(in_feature_class, out_folder + os.sep + \ feature_class_name) # If successful, add another message # arcpy.AddMessage("Successfully converted: " + in_feature_class + \ " To " + out_folder) except StandardError, ErrDesc: arcpy.AddWarning("Failed to convert: " + in_feature_class) arcpy.AddWarning(ErrDesc) except: arcpy.AddWarning("Failed to convert: " + in_feature_class) if not arcpy.GetMessages(2) == "": arcpy.AddError(arcpy.GetMessages(2))
Voici le module ScriptUtils utilisé ci-dessus.
# ScriptUtils.py # Import required modules # import arcpy import sys import string import os def SplitMulti(multi_input): try: # Split the multivalue on the semi-colon delimiter # multi_as_list = string.split(multi_input, ";") return multi_as_list except: ErrDesc = "Error: Failed in parsing the inputs." raise StandardError, ErrDesc def CopyFeatures(in_table, out_table): try: ErrDesc = "CopyFeatures failed" # Copy each feature class to the output workspace # arcpy.CopyFeatures_management(in_table, out_table) except: if arcpy.GetMessages(2) != "": ErrDesc = arcpy.GetMessages(2) raise StandardError, ErrDesc
Remarque à propos des scripts précédents
Les fonctions, les modules et les objets peuvent être importés à partir d'autres scripts Python afin de simplifier votre script et centraliser le code. L'instruction import sert à extraire tous les éléments d'un autre script ou uniquement les entités de votre choix.
Les exceptions représentent des événements permettant de modifier le flux de contrôle d'un programme. Elles peuvent être déclenchées ou interceptées dans un script à l'aide des instructions try et raise. L'exception StandardError est une exception intégrée sur laquelle la plupart des types d'exceptions sont basés dans Python. Elle est appelée si une erreur d'outil se produit, la variable ErrDesc étant définie par le message d'erreur de l'outil.
Pour en savoir plus sur la gestion des erreurs dans Python
Les chaînes, définies dans le module string, constituent un type intégré utilisé pour stocker et représenter du texte. Plusieurs opérations permettent de gérer les chaînes, telles que la concaténation, le découpage et l'indexation.
Le module système d'exploitation (os) offre une interface générique pour le jeu d'outils de base du système d'exploitation.
Renvoi de tous les messages à partir d'un outil
Il se peut que vous vouliez renvoyer tous les messages à partir d'un outil vous avez appelé, quelle que soit la gravité du message. En utilisant un paramètre d'index, la fonction AddReturnMessage renvoie un message issu du tableau de messages d'ArcPy. L'exemple ci-dessous montre comment renvoyer tous les messages d'un outil :
arcpy.Clip_analysis("roads","urban_area","urban_roads") # Return the resulting messages as script tool output messages # x = 0 while x < arcpy.MessageCount: arcpy.AddReturnMessage(x) x = x + 1
Messagerie dans un script à double fonction
Vous pouvez concevoir votre script dans deux objectifs : il peut être utilisé comme script autonome (à partir du système d'exploitation) ou comme outil de script. Vous devez prendre en compte trois éléments :
- Lorsque le script est exécuté en tant que script autonome, il est impossible d'afficher les messages (il n'existe aucune application, telle qu'ArcMap, dans laquelle les messages peuvent être affichés). A la place, vous pouvez utiliser une instruction print afin d'afficher les messages dans la fenêtre de commande (également appelée sortie standard).
- Lorsque le script est exécuté en tant qu'outil, les messages s'affichent dans la boîte de dialogue de progression, la fenêtre Résultats et la fenêtre Python (si l'outil est exécuté dans la fenêtre Python). Vous n'avez pas besoin d'utiliser d'instruction d'impression. Si vous vous appuyez uniquement sur les instructions d'impression, activez la case à cocher Afficher la fenêtre de commande lors de l'exécution du script dans l'onglet Source des propriétés de l'outil. Il est recommandé de ne pas afficher la fenêtre de commande pendant l'exécution d'un outil de script : cela n'est pas très esthétique et la fenêtre disparaît souvent de façon inopportune.
- Lors de l'exécution du script en tant que script autonome, vous devez générer une exception afin que le programme qui appelle puisse le détecter. Lors de l'exécution du script en tant qu'outil de script, vous utilisez la fonction AddError() pour générer l'exception.
En règle générale, vous devez écrire une routine de rapport d'erreur qui écrit des messages dans la sortie standard (à l'aide de l'instruction d'impression) et par le biais d'ArcPy (à l'aide des fonctions AddMessage, AddWarning et AddError). Ci-après vous trouverez un exemple d'une telle routine, issue de l'outil Anneaux concentriques multiples, un outil de script disponible dans la boîte à outils Analysis. Vous pouvez afficher le script Anneaux concentriques multiples en recherchant l'outil dans la boîte à outils Analysis, en cliquant dessus avec le bouton droit, puis en cliquant sur Modifier.
def AddMsgAndPrint(msg, severity=0): # Adds a Message (in case this is run as a tool) # and also prints the message to the screen (standard output) # print msg # Split the message on \n first, so that if it's multiple lines, # a GPMessage will be added for each line try: for string in msg.split('\n'): # Add appropriate geoprocessing message # if severity == 0: arcpy.AddMessage(string) elif severity == 1: arcpy.AddWarning(string) elif severity == 2: arcpy.AddError(string) except: pass
Contrôle de la boîte de dialogue de progression
Etant donné que les outils de script partagent l'application, vous pouvez contrôler la boîte de dialogue de progression. Vous pouvez contrôler l'apparence de la boîte de dialogue de progression en sélectionnant la barre de progression par défaut ou par étape, comme illustré ci-dessous.
Quatre fonctions permettent de contrôler la boîte de dialogue de progression et sa barre de progression.
Fonction |
Description |
---|---|
Définit le type de barre de progression (par défaut ou par étape), son étiquette et le minimum, le maximum et l'intervalle pour les barres de progression par étape |
|
Réinitialise la barre de progression |
|
Avance la barre de progression par étape d'un incrément |
|
Modifie l'étiquette de la barre de progression |
Le code suivant montre l'utilisation complète de la barre de progression par défaut et par étape. Copiez ce code dans votre éditeur Python, enregistrez-le, puis créez-lui un outil de script. L'outil de script dispose de deux paramètres Long en entrée, comme décrit dans les remarques du code. Exécutez ensuite l'outil de script, en fournissant des valeurs différentes pour les paramètres (commencez avec n = 10 et p = 1, puis essayez n = 101 et p = 3).
# Demonstration script showing examples of using the progressor # Parameters: # n - number to count to (a good first choice is 10) # p - interval to count by (a good first choice is 1) # The various time.sleep() calls are just to slow the dialog down # so you can view messages and progressor labels. # import arcpy import time n = int(arcpy.GetParameterAsText(0)) p = int(arcpy.GetParameterAsText(1)) readTime = 2.5 # Pause to read what's written on dialog loopTime = 0.3 # Loop iteration delay arcpy.AddMessage("Running demo with: " + str(n) + " by " + str(p)) # Start by showing the default progress dialog, where the # progress bar goes back and forth. Note how the progress label # mimics working through some "phases", or chunks of work that # a script may perform. # arcpy.SetProgressor("default", "This is the default progressor") time.sleep(readTime) for i in range(3): arcpy.SetProgressorLabel("Working on \"phase\" " + str(i + 1)) arcpy.AddMessage("Messages for phase " + str(i+1)) time.sleep(readTime) arcpy.AddMessage("-------------------------") # Setup the progressor with its initial label, min, max, and interval # arcpy.SetProgressor("step", "Step progressor: Counting from 0 to " + str(n), 0, n, p) time.sleep(readTime) # Loop issuing a new label when the increment is divisible by the # value of countBy (p). The "%" is python's modulus operator - we # only update the position every p'th iteration # for i in range(n): if (i % p) == 0: arcpy.SetProgressorLabel("Iteration: " + str(i)) arcpy.SetProgressorPosition(i) time.sleep(loopTime) # Update the remainder that may be left over due to modulus operation # arcpy.SetProgressorLabel("Iteration: " + str(i+1)) arcpy.SetProgressorPosition(i+1) arcpy.AddMessage("Done counting up") arcpy.AddMessage("-------------------------") time.sleep(readTime) # Just for fun, make the progressor go backwards. # arcpy.SetProgressor("default", "Default progressor: Now we'll do a countdown") time.sleep(readTime) arcpy.AddMessage("Here comes the countdown...") arcpy.SetProgressor("step", "Step progressor: Counting backwards from " + str(n), 0, n, p) time.sleep(readTime) arcpy.AddMessage("Counting down now...") for i in range(n, 0, -1): if (i % p) == 0: arcpy.SetProgressorLabel("Iteration: " + str(i)) arcpy.SetProgressorPosition(i) time.sleep(loopTime) # Update for remainder # arcpy.SetProgressorLabel("Iteration: " + str(i-1)) arcpy.SetProgressorPosition(i-1) time.sleep(readTime) arcpy.AddMessage("-------------------------") arcpy.AddMessage("All done") arcpy.ResetProgressor()
Sélection d'un incrément adapté lorsque le maximum est potentiellement élevé
Il n'est pas rare d'écrire des scripts qui se répéteront un nombre inconnu de fois. Par exemple, votre script peut utiliser une commande SearchCursor pour itérer sur toutes les lignes d'une table, sans savoir à l'avance le nombre de lignes : votre script peut être utilisé avec des tables de toute taille, de plusieurs milliers de lignes à des millions de lignes. Incrémenter une barre de progression par étape pour chaque ligne d'une grande table génère un ralentissement des performances, et vous devez vous en prémunir.
Pour démontrer et évaluer les performances avec les barres de progression par étape, copiez le code ci-dessous dans votre éditeur Python, enregistrez-le, puis créez un outil de script. L'outil a deux entrées : un paramètre de table et un paramètre de champ. Exécutez l'outil de script avec des tables de différentes tailles, mais veillez à essayer une table ou une classe d'entités contenant au moins 10 000 lignes pour voir les différences de performances. (Vous pouvez également essayer d'exécuter l'outil en cours de processus et hors processus pour constater l'amélioration des performances lors de l'exécution en cours de processus.)
Le script exécute trois boucles distinctes et chaque boucle extrait toutes les lignes de la table. Les boucles diffèrent dans leur façon de mettre à jour la barre de progression par étape. La première et la deuxième boucle mettent à jour la barre de progression par étape par grands incréments, tandis que la dernière boucle incrémente la barre de progression par étape pour chaque ligne. Lorsque vous exécutez l'outil, vous verrez que l'exécution de cette dernière boucle est plus longue.
Vous pouvez employer les techniques figurant dans ce code pour vos outils de script.
# Demonstrates a step progressor by looping through records # on a table. Use a table with 10,000 or so rows - smaller tables # just whiz by. # 1 = table name # 2 = field on the table import arcpy try: inTable = arcpy.GetParameterAsText(0) inField = arcpy.GetParameterAsText(1) # Determine n, number of records on the table # arcpy.AddMessage("Getting row count") n = arcpy.GetCount_management(inTable) if n == 0: raise "no records" arcpy.AddMessage("Number of rows = " + str(n)) arcpy.AddMessage("") arcpy.AddMessage("---------------------------------") # Method 1: Calculate and use a suitable base 10 increment # =================================== import math p = int(math.log10(n)) if not p: p = 1 increment = int(math.pow(10, p-1)) arcpy.SetProgressor("step", "Incrementing by " + str(increment) + " on " + \ inTable, 0, n, increment) rows = arcpy.SearchCursor(inTable) i = 0 beginTime = time.clock() for row in rows: if (i % increment) == 0: arcpy.SetProgressorPosition(i) fieldValue = row.getValue(inField) i = i + 1 arcpy.SetProgressorPosition(i) arcpy.AddMessage("Method 1") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elapsed time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") del rows del row # Method 2: let's just move in 10 percent increments # =================================== increment = int(n/10.0) arcpy.SetProgressor("step", "Incrementing by " + str(increment) + " on " + inTable, \ 0, n, increment) rows = arcpy.SearchCursor(inTable) i = 0 beginTime = time.clock() for row in rows: if (i % increment) == 0: arcpy.SetProgressorPosition(i) fieldValue = row.getValue(inField) i = i + 1 arcpy.SetProgressorPosition(i) arcpy.AddMessage("Method 2") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elapsed time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") del rows del row # Method 3: use increment of 1 # =================================== increment = 1 arcpy.SetProgressor("step", "Incrementing by 1 on " + inTable, 0, n, increment) rows = arcpy.SearchCursor(inTable) beginTime = time.clock() while row: arcpy.SetProgressorPosition() fieldValue = row.getValue(inField) arcpy.SetProgressorPosition(n) arcpy.ResetProgressor() arcpy.AddMessage("Method 3") arcpy.AddMessage("Increment = " + str(increment)) arcpy.AddMessage("Elasped time: " + str(time.clock() - beginTime)) arcpy.AddMessage("---------------------------------") arcpy.AddMessage("") arcpy.AddMessage("Pausing for a moment to allow viewing...") time.sleep(2.0) # Allow viewing of the finished progressor del rows del row except "no records": arcpy.AddWarning(inTable + " has no records to count") except: if rows: del rows if row: del row arcpy.AddError("Exception occurred")