使用脚本工具编写消息

在脚本中,需要导入 ArcPy,如下所示:

import arcpy

当脚本作为脚本工具运行时,ArcPy 完全知道调用它的应用程序(例如 ArcMap 或 ArcCatalog)。实际上,是在使用与应用程序正在使用的对象相同的对象,而非创建新对象。在应用程序中设置的所有环境设置(例如 overwriteOutputscratchWorkspace)均可用。这一事实的一个主要作用是您可以通过 ArcPy 写入消息,并且您的消息会自动出现在进度对话框、工具结果和 Python 窗口中。这还表示调用脚本工具的任何模型或脚本工具均有权访问您所写入的消息。

将此写入消息的脚本与独立脚本进行对比。在独立脚本中,您的脚本不是通过 ArcGIS 应用程序进行调用,因此,在可以查看消息的位置不存在进度对话框、结果或 Python 窗口。如果在您的脚本中调用其他脚本(独立脚本),则调用的脚本(如果使用地理处理工具)完全独立,这不同于您所创建的脚本,它们之间没有共享消息。脚本工具的主要优势之一是消息和环境的共享。

用于写入消息的四个 ArcPy 函数如下所示:

添加消息示例

下面的示例是将一列要素类从一个工作空间复制到另一个工作空间。如果工作空间类型不同,例如从地理数据库复制到文件夹,则会自动进行转换。错误处理用于发现任何错误并返回消息;否则,执行过程中将返回指示执行成功的信息性消息。本例使用 from <module> import 指令。在这种情况下,模块是 ScriptUtils,其代码也在下面列出。此代码也使用 try/except 块。

了解有关“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))

以下是上面使用过的 ScriptUtils 模块。

# 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

上述脚本的注释

可从其他 Python 脚本导入函数、模块和对象以简化您的脚本并使代码集中。import 语句用于从所需的其他脚本或仅从实体中获取所需的任何信息。

异常是可以通过程序修改控制流的事件。在脚本中使用 tryraise 语句可触发或拦截这些异常。StandardError 是内置异常,Python 中大多数类型的异常都基于该异常。如果由工具的错误消息设置的 ErrDesc 变量发生工具错误,则将调用该异常。

了解有关使用 Python 进行错误处理的详细信息

string 模块中定义的字符串是用于存储和表示文本的内置输入。有大量操作支持对字符串进行操作,例如组合、划分和建立索引。

操作系统 (os) 模块为使用操作系统的基本工具集提供了一个通用接口。

返回工具的所有消息

有时您可能想要返回所调用的工具的所有消息,而不考虑消息的严重性。通过索引参数,AddReturnMessage 函数可返回 ArcPy 的消息数组的消息。以下示例显示返回工具的所有消息的方法:

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

双重用途脚本中的消息

为了实现双重用途,您可以按以下方式设计您的脚本:该脚本可用作独立脚本(通过操作系统)或脚本工具。有以下三个注意事项:

标准做法是写入错误报告例程,该例程会以标准输出(使用 print)和使用 ArcPy(使用 AddMessageAddWarningAddError 函数)两种方式写入消息。此处就是这样的例程,取自多环缓冲区工具(“分析”工具箱中的脚本工具)。您可以查看多环缓冲区脚本,方法是在“分析”工具箱中找到该工具,右键单击,然后单击编辑

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

控制进度对话框

由于脚本工具共享了应用程序,因此您可以控制进度对话框。您可以通过选择默认进度条或步骤进度条来控制进度对话框的外观,如下图所示。

默认进度条和步骤进度条

用于控制进度对话框及其进度条的函数有四种。

函数

描述

SetProgressor

设置进度条类型(默认或步骤);进度条标注;以及步骤进度条的最小值、最大值和间隔值

ResetProgressor

重置进度条

SetProgressorPosition

按增量移动步骤进度条

SetProgressorLabel

更改进度条的标注

地理处理进度条函数

以下代码说明了对默认进度条和步骤进度条的充分使用。将此代码复制到 Python 编辑器中,进行保存,然后为其创建脚本工具。脚本工具包含两个长整型输入参数,如代码注释中所述。然后运行脚本工具,同时为参数提供不同值(以 n = 10 和 p = 1 开始,然后尝试 n = 101 和 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()

最大值可能很大时选择一个合适的增量

编写迭代次数未知的脚本并不少见。例如,脚本可以使用 SearchCursor 迭代表中的所有行,而您事先并不知道有多少行,也就是说您的脚本可用于迭代从几千行到数百万行的任意大小的表。为大型表中的每行增大步长进度条会出现性能瓶颈问题,最好防止出现此类性能瓶颈。

要演示和评估步长进度条的性能问题,请将下面的代码复制到 Python 编辑器中,然后对其进行保存,之后为其创建一个脚本工具。该工具有两个输入:表参数和字段参数。以不同表大小运行该脚本工具,但一定要尝试使用包含 10000 行或更多行的表或要素类来查看性能差异。(还可以尝试进程内和进程外运行该工具以了解进程内运行时的性能提高。)

该脚本执行三个独立的循环,每个循环都提取表中的所有行。各个循环在更新步长进度条的方式上有所不同。第一个和第二个循环以较大的增量更新步长进度条,最后一个循环每行增大一次步长进度条。运行该工具时,您会看到最后一个循环运行时间较长。

最好对该脚本工具使用此代码提供的技术。

# 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