Escribir mensajes en las herramientas de secuencia de comandos
Debe importar ArcPy en las secuencias de comandos de la manera siguiente:
import arcpy
Cuando la secuencia de comandos se ejecuta como una herramienta de secuencia de comandos, ArcPy estará completamente al tanto de la aplicación que la invoca, como ArcMap o ArcCatalog. En realidad, está utilizando los mismos objetos que la aplicación en lugar de crear unos nuevos. Toda la configuración del entorno que se hace en la aplicación, como overwriteOutput y scratchWorkspace, se encuentra disponible. Un efecto principal de esto es que puede escribir mensajes con ArcPy y éstos aparecen automáticamente en el cuadro de diálogo de progreso, en el resultado de la herramienta y en la ventana de Python. También significa que cualquier modelo o herramienta de secuencia de comandos que invoque la herramienta de secuencia de comandos tiene acceso a los mensajes que usted escribe.
Compare esto con una secuencia de comandos independiente. En una secuencia de comandos independiente, la secuencia de comandos no se invoca desde una aplicación de ArcGIS. Por lo tanto, no hay cuadro de diálogo de progreso, resultado o ventana de Python en donde se puedan visualizar los mensajes. Si invoca otra secuencia de comandos (independiente) desde dentro de la secuencia de comandos, la secuencia de comandos invocada (si utiliza herramientas de geoprocesamiento) está completamente separada, es diferente de la que usted creó, y no se comparten los mensajes entre ellas. Una de las ventajas más importantes de las herramientas de secuencia de comandos es que se pueden compartir los mensajes y los entornos.
Las cuatro funciones de ArcPy para escribir mensajes son las siguientes:
- AddMessage("mensaje"): para mensajes informativos en general (gravedad = 0).
- AddWarning("mensaje"): para advertencias (gravedad = 1).
- AddError("mensaje"): para errores (gravedad = 2).
- AddIDMessage(MessageType, MessageID, AddArgument1, AddArgument2): utilizado tanto para errores como para advertencias (el argumento MessageType determina la gravedad). Una llamada a AddIDMessage() mostrará un mensaje corto y el Id. de mensaje, que es un vínculo a una explicación sobre el motivo y las soluciones del problema. Cuando agrega un mensaje de error (utilizando AddError() o AddIDMessage()), ocurre lo siguiente:
- Continúa la ejecución de la secuencia de comandos. Queda a su criterio agregar la lógica de manejo de errores adecuada y detener la ejecución de la secuencia de comandos. Por ejemplo, es posible que deba eliminar cursores o archivos intermedios.
- Después de la devolución de la secuencia de comandos, la secuencia de comandos o el modelo que hace la llamada reciben un error de sistema y la ejecución se detiene.
Ejemplo de agregar mensajes
El ejemplo siguiente copia una lista de clases de entidad de un espacio de trabajo a otro. Ocurre una conversión automática si los espacios de trabajo son de un tipo distinto, como de geodatabase a carpeta. El manejo de errores se utiliza para detectar problemas y devolver mensajes; en caso contrario, los mensajes informativos de éxito se devuelven durante la ejecución. En este ejemplo se utiliza la directiva importar desde <módulo>. En este caso, el módulo es ScriptUtils, y su código también se indica a continuación. Este código también utiliza bloques try/except .
Más información sobre los bloques 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))
A continuación se encuentra el módulo ScriptUtils utilizado anteriormente.
# 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
Notas sobre las secuencias de comando anteriores
Las funciones, los módulos y los objetos se pueden importar desde otras secuencias de comando de Python para simplificar la secuencia de comandos y centralizar el código. La instrucción import se utiliza para extraer toda la información de otra secuencia de comandos o sólo las entidades que desee.
Las excepciones son eventos que pueden modificar el flujo de control mediante un programa. Pueden desencadenarse o interceptarse dentro de una secuencia de comandos mediante las instrucciones try y raise . StandardError es una excepción integrada en la cual se basan la mayoría de los tipos de excepción en Python. Se invoca si ocurre un error de herramienta con la variable ErrDesc establecida por el mensaje de error de la herramienta.
Más información sobre cómo manejar errores en Python
Las cadenas de texto, definidas en el módulo string , son un tipo integrado utilizado para almacenar y representar texto. Se admiten varias operaciones para manipular las cadenas de texto, como la concatenación, la segmentación y la indexación.
El módulo del sistema operativo (os) le suministra una interfaz genérica al conjunto de herramientas básico del sistema operativo.
Devolver todos los mensajes de una herramienta
Hay veces en las que puede desear devolver todos los mensajes de la herramienta que invocó, sin importar la gravedad del mensaje. Al utilizar un parámetro de índice, la función AddReturnMessage devuelve un mensaje desde el conjunto de mensajes de ArcPy. El siguiente ejemplo muestra cómo devolver todos los mensajes de una herramienta:
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
Mensajes en una secuencia de comandos de propósito dual
Puede diseñar la secuencia de comandos para propósitos duales: se puede utilizar como secuencia de comandos independiente (del sistema operativo) o como una herramienta de secuencia de comandos. Existen tres consideraciones:
- Cuando la secuencia de comandos se ejecuta de manera independiente, no hay forma de ver los mensajes (no existe una aplicación como ArcMap, en la cual se pueden visualizar los mensajes). En su lugar, puede utilizar una instrucción imprimir para que los mensajes se puedan visualizar en la ventana de comandos (que también se conoce como salida estándar).
- Cuando la secuencia de comandos se ejecuta como una herramienta, los mensajes aparecen en el cuadro de diálogo de progreso, en la ventana Resultados y en la ventana de Python (si la herramienta se está ejecutando en la ventana de Python). No es necesario utilizar una instrucción print. Si utiliza únicamente en instrucciones de impresión, debe marcar el cuadro de diálogo Mostrar la ventana del comando cuando se ejecute la secuencia de comandos que se encuentra en la ficha Fuente de las propiedades de la herramienta. Mostrar la ventana del comando cuando se ejecute una herramienta de secuencia de comandos no es una buena práctica; tiene una apariencia desagradable y generalmente desaparece cuando usted no lo desea.
- Al ejecutarse como una secuencia de comandos independiente, desea generar una excepción para que el programa que la invoca pueda detectarlo. Al ejecutarse como una herramienta de secuencia de comandos, utilice la función AddError() para generar la excepción.
La práctica estándar consiste en escribir una rutina de informes de error que escriba mensajes en la salida estándar (mediante print) y por medio de ArcPy (mediante las funciones AddMessage, AddWarning y AddError). A continuación se presenta una rutina de ese tipo, tomada de la herramienta Zona de influencia en anillos múltiples, una herramienta de secuencia de comandos de la caja de herramientas Análisis. Puede visualizar la secuencia de comandos de Zona de influencia en anillos múltiples si ubica la herramienta en la caja de herramientas Análisis, hace clic con el botón derecho del ratón y a continuación hace clic en Editar.
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
Controlar el cuadro de diálogo de progreso
Debido a que las herramientas de secuencia de comandos comparten la aplicación, usted tiene control sobre el cuadro de diálogo de progreso. Puede controlar la apariencia del cuadro de diálogo de progreso seleccionando el indicador de progreso predeterminado o el indicador de progreso por pasos, como se muestra a continuación.
Existen cuatro funciones que puede utilizar para controlar el cuadro de diálogo de progreso y su indicador de progreso.
Función |
Descripción |
---|---|
Establece el tipo de indicador de progreso (predeterminado o por pasos); su etiqueta y el mínimo, el máximo y el intervalo para los indicadores de progreso por pasos |
|
Restablece el indicador de progreso |
|
Mueve el indicador de progreso por pasos según un incremento |
|
Cambia la etiqueta del indicador de progreso |
El siguiente código demuestra el uso completo de los indicadores de progreso predeterminado y por pasos. Copie este código en el editor de Python, guárdelo y a continuación cree una herramienta de secuencia de comandos para él. La herramienta de secuencia de comandos tiene dos parámetros de entrada largos como se describe en los comentarios del código. A continuación, ejecute la herramienta de secuencia de comandos, y suministre valores diferentes para los parámetros (comience con n = 10 y p = 1, después pruebe con n = 101 y 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()
Seleccionar un incremento adecuado cuando el máximo puede ser grande
Es bastante común escribir secuencias de comandos que iteren una cantidad de veces no conocida. Por ejemplo, la secuencia de comandos puede utilizar un SearchCursor para iterar por todas las filas de una tabla y no se conoce la cantidad de filas de antemano: la secuencia de comandos se puede utilizar con tablas de cualquier tamaño, desde varios miles de filas hasta millones de filas. Incrementar un indicador de progreso por pasos para cada fila en una tabla grande es un cuello de botella de rendimiento y es posible que usted desee evitar tales situaciones.
Para demostrar y evaluar los problemas de rendimiento con indicadores de progreso por pasos, copie el siguiente código en el editor de Python, guárdelo, y a continuación cree una herramienta de secuencia de comandos para él. La herramienta tiene dos entradas: un parámetro de tabla y un parámetro de campo. Ejecute la herramienta de secuencia de comandos con una variedad de tamaños de tablas, pero asegúrese de intentar con una clase de entidad o tabla que contenga 10.000 filas o más para ver las diferencias en el rendimiento. (También puede intentar la ejecución de la herramienta dentro y fuera del proceso para evaluar el aumento de rendimiento al ejecutarla dentro del proceso).
La secuencia de comandos ejecuta tres bucles separados y cada bucle captura todas las filas de la tabla. Los bucles se diferencian en la manera de actualizar el indicador de progreso por pasos. El primer y segundo bucle actualizan el indicador de progreso por pasos en incrementos grandes y el último bucle incrementa el indicador de progreso por pasos una vez para cada fila. Cuando ejecute la herramienta, verá que este último bucle tarda más en ejecutarse.
Es posible que desee emplear las técnicas que se encuentran en este código en las herramientas de secuencia de comandos.
# 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")