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 :

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 :

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.

Barre de progression par défaut et par étape

Quatre fonctions permettent de contrôler la boîte de dialogue de progression et sa barre de progression.

Fonction

Description

SetProgressor

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

ResetProgressor

Réinitialise la barre de progression

SetProgressorPosition

Avance la barre de progression par étape d'un incrément

SetProgressorLabel

Modifie l'étiquette de la barre de progression

Fonctions de la barre de progression de géotraitement

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")

7/10/2012