スクリプト ツールでのメッセージの書き込み

スクリプトで ArcPy をインポートするには、次のように指定する必要があります。

import arcpy

スクリプトをスクリプト ツールとして実行すると、ArcMap や ArcCatalog などの呼び出し元アプリケーションが、ArcPy に完全に認識されます。実際に使用されるオブジェクトは、アプリケーションが使用しているのと同じオブジェクトです(新規のオブジェクトは作成されません)。たとえば、overwriteOutputscratchWorkspace など、アプリケーションで行ったすべての環境設定が使用できます。ArcPy でメッセージを作成し、その自作メッセージを進捗状況ダイアログ ボックス、ツールの結果ウィンドウ、および Python ウィンドウに自動的に表示できることは、スクリプト ツールによる大きな効果の 1 つです。また、スクリプト ツールでは、スクリプト ツールの呼び出し元であるモデルまたはスクリプト ツールのいずれからも自作メッセージにアクセスできます。

スクリプト ツールとは対照的に、スタンドアロン スクリプトでは ArcGIS アプリケーションからスクリプトが呼び出されるので、メッセージの閲覧が可能な進捗状況ダイアログ ボックス、結果ウィンドウ、または Python ウィンドウはありません。スクリプト内から別の(スタンドアロン)スクリプトを呼び出した場合、呼び出されたスクリプト(ジオプロセシング ツールを使用するスクリプト)が自作メッセージとはまったく違う別個のものであるため、スクリプト間でのメッセージ共有は不可能です。スクリプト ツールを利用すれば、その主要メリットの 1 つであるメッセージ共有および環境共有を実現できます。

メッセージ作成用の ArcPy 関数は、次の 4 通りです。

メッセージの追加の例

次の例では、あるワークスペースから別のワークスペースへ、フィーチャクラスのリストをコピーします。ジオデータベースからフォルダのように、2 つのワークスペースが異なるタイプの場合、自動的に変換されます。エラー処理は、問題を捕捉し、メッセージを返すために使用されます。問題がなければ、実行中に正常処理の情報メッセージが返されます。この例では、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 ステートメントを使用すると、別のスクリプトからすべて、あるいは必要なエンティティだけを利用できます。

例外は、プログラムの制御フローを変更するイベントです。例外は、try および raise ステートメントを使用して、スクリプト内で発動またはインターセプトされます。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

2 つの目的を兼ねるスクリプトのメッセージング

スクリプトは、スタンドアロン スクリプト(オペレーティング システムから利用)またはスクリプト ツールという 2 つの目的で使用できるように設計できます。これには、考慮すべきことが 3 つあります。

一般的には、メッセージを標準出力(print を使用)と ArcPy(AddMessageAddWarningAddError の各関数を使用)の両方で書き込むエラー報告ルーチンを記述します。以下は、[解析] ツールボックスの [多重リング バッファ(Multiple Ring Buffer)] ツールの中から、このようなルーチンを抜粋したものです。[多重リング バッファ(Multiple Ring Buffer)] スクリプトを表示するには、[解析] ツールボックスを右クリックして、[編集] をクリックします。

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

進捗状況ダイアログ ボックスの制御

スクリプト ツールはアプリケーションを共有するので、進捗状況ダイアログ ボックスを制御できます。次の図のように、デフォルト プログレッサとステップ プログレッサのいずれかを選択することで、進捗状況ダイアログ ボックスの外観を制御できます。

デフォルト プログレッサとステップ プログレッサ

進捗状況ダイアログ ボックスとそのプログレッサを制御するには、4 つの関数を使用します。

機能

説明

SetProgressor

プログレッサのタイプ(デフォルトまたはステップ)、ラベル、最小値と最大値、ステップ プログレッサの場合は間隔を設定します。

ResetProgressor

プログレッサをリセットします

SetProgressorPosition

ステップ プログレッサを増分だけ移動します

SetProgressorLabel

プログレッサのラベルを変更します

ジオプロセシングのプログレッサ関数

次のコードは、デフォルト プログレッサとステップ プログレッサの使用法を示したものです。このコードを Python エディタにコピーし、保存してから、そのスクリプト ツールを作成してください。コードのコメントに説明されているように、このスクリプト ツールには、Long の 2 つの入力パラメータがあります。次に、スクリプト ツールを実行し、パラメータに異なる値を設定します(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 エディタにコピーし、保存してから、そのスクリプト ツールを作成します。このツールには、テーブル パラメータとフィールド パラメータの 2 つの入力があります。スクリプト ツールをさまざまなサイズのテーブルを使用して実行します。必ず 10,000 以上の行を含むテーブルまたはフィーチャクラスを試して、パフォーマンスの違いを調べます(ツールをプロセス内とプロセス外で実行してみて、プロセス内で実行した場合のパフォーマンスの向上を確認することもできます)。

このスクリプトは、3 つのループを実行します。各ループでは、テーブルのすべての行を取得しています。これらのループは、ステップ プログレッサの更新方法が異なります。最初と 2 番目のループは、ステップ プログレッサを大きな増分で更新しています。最後のループは、各行ごとにステップ プログレッサを増加させています。ツールを実行すると、この最後のループの実行に時間がかかることがわかります。

このコードにある手法をスクリプト ツールに利用してください。

# 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