Hello all,
I'm hoping someone can tell me what the "self" is referring to in a Toolbox's Tool's method definitions. It's not what I expect it to be.
Below is an example with regards to a property set on the Tool object in its getParameterInfo method using "self". The following is the smallest example I could come up with to illustrate the behaviour which is unexpected to me. I can perform the following steps in both ArcPro 3.0.2 and ArcCatalog 10.4.1 with the same results, so this is not new. The code I provide at the end of this post will run in either product as is.
1. In ArcPro or ArcCatalog (or ArcMap, I assume), create a new Python Toolbox to generate a pyt file that includes default class definitions for Toolbox and Tool objects. The following code changes are all within the Tool class' definition.
2. Add a version property to Tool and set its value to 1;
3. In Tool's getParameterInfo(self) method, add an arcpy.Parameter to ensure its code is running and before returning the parameter, set self.version to 100;
4. In Tool's execute method, add an arcpy message to print out the value in self.version.
5. Run the tool. No need to make any changes in its GUI, just load it, run it and look at its output messages.
When I run this tool, the value that is output by the "print" message is 1. I expect it to be 100.
As a further test, I put "self.version = " different values in updateParameters and updateMessages and tried changing the default value in the GUI's Version input box to get them to run. Still, when I run the task, the output tells me the value in self.version is 1.
Finally, in Tool's __init__ method, I set self.version to 2. That line is in the code below also, but is currently commented out. Uncomment it to try it yourself. This time, the "print" output 2, so updating the self.version property in the __init__ method works as I expect but not when done in any other Tool method.
Can anyone explain why it is not outputing 100 in my original test above? What is "self" referring to in those the Tool class methods' parameter lists? Or this there something wrong with how I am tryign to use it?
Regards,
# -*- coding: utf-8 -*-
import arcpy
class Toolbox(object):
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
self.label = "Toolbox"
self.alias = "toolbox"
# List of tool classes associated with this toolbox
self.tools = [Tool]
class Tool(object):
# ------------------------------------------
# Added and initialized this property
# ------------------------------------------
version = 1
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Tool"
self.description = ""
self.canRunInBackground = False
# -----------------------------------------
# Added this line
# -----------------------------------------
# self.version = 2
def getParameterInfo(self):
"""Define parameter definitions"""
# ------------------------------------------
# This whole method is new
# ------------------------------------------
# params = None
# return params
version_param = arcpy.Parameter(
displayName="Version",
name="version",
datatype="GPString",
parameterType="Optional",
direction="Input")
version_param.value = "testing, testing, 123 ..."
self.version = 100
return [version_param]
def isLicensed(self):
"""Set whether tool is licensed to execute."""
return True
def updateParameters(self, parameters):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
return
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
return
def execute(self, parameters, messages):
"""The source code of the tool."""
# ------------------------------------------
# Added this line
# ------------------------------------------
arcpy.AddMessage("execute: " + str(self.version))
return
def postExecute(self, parameters):
"""This method takes place after outputs are processed and
added to the display."""
return
Solved! Go to Solution.
I have run into the same problem in the past.
The way I dealt with this is defining an empty class in the toolbox. You can set and get class attributes for this class in the tool methods.
class Toolbox:
def __init__(self):
self.label = "Toolbox"
self.alias = "toolbox"
self.tools = [Tool]
class ToolboxData:
pass
class Tool:
def __init__(self):
self.label = "Tool"
self.canRunInBackground = False
def getParameterInfo(self):
version_param = arcpy.Parameter(
displayName="Version",
name="version",
datatype="GPString",
parameterType="Optional",
direction="Input")
version_param.value = "testing, testing, 123 ..."
ToolboxData.version = 100
return [version_param]
def execute(self, parameters, messages):
arcpy.AddMessage("execute: " + str(ToolboxData.version))
You are setting version as a Class Attribute, which acts a little differently than a property and would need to referred to with the Class Name.version, so Tool.version = 100. Think it has something to do with how the tools are ingested into the apps pipeline.
After thinking about this some more, you are also trying to set an attribute in a getter method that is designed to just return a list of parameters for the UI or in code. If you want to change the version (either class or instance), I think you need to do this in the execute method:
class Tool(object):
# ------------------------------------------
# Added and initialized this property
# ------------------------------------------
ClassVersion = 1
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Tool"
self.description = ""
self.canRunInBackground = False
# -----------------------------------------
# Added this line
# -----------------------------------------
self.version = 1
def getParameterInfo(self):
"""Define parameter definitions"""
# ------------------------------------------
# This whole method is new
# ------------------------------------------
# params = None
# return params
version_param = arcpy.Parameter(
displayName="Version",
name="version",
datatype="GPString",
parameterType="Optional",
direction="Input")
version_param.value = "testing, testing, 123 ..."
return [version_param]
...
def execute(self, parameters, messages):
"""The source code of the tool."""
# ------------------------------------------
# Added this line
# ------------------------------------------
arcpy.AddMessage("pre execute Tool.version: " + str(self.ClassVersion))
arcpy.AddMessage("pre execute self.version1: " + str(self.version))
self.version = 100
Tool.ClassVersion = 300
arcpy.AddMessage("execute Tool.version: " + str(self.ClassVersion))
arcpy.AddMessage("execute self.version1: " + str(self.version))
return
...
Results:
Start Time: Sunday, February 19, 2023 2:29:28 PM
pre execute Tool.version: 1
pre execute self.version1: 1
execute Tool.version: 300
execute self.version1: 100
Succeeded at Sunday, February 19, 2023 2:29:28 PM (Elapsed Time: 0.03 seconds)
Thank you for your responses, Jeff!
I recognize that what I am doing here is odd but it was only meant to illustrate what I was running into in the simplest example possible. In reality, I'm using information from a couple of places to determine which parameters are actually being added to the tool at runtime and don't want to have to duplicate the exact same code in the execute to figure out which parameters to check for input values and how to interpret them. What's in parameter 2 during one execution is not necessarily in parameter 2 the next time...it might be in parameter 3.
The information in your first post, that I was employing a Class Attribute, rather than a property was useful to know. I think I might be getting languages mixed up because I thought I remembered defining classes that way in Python before but I'm sure not now.
In the end, I went with Johannes suggestion. I have a number of these values that I want to store while building the parameter list and that seemed the least complicated and most easily understood by anyone who has to update this code later.
Cheers
jtm
I have run into the same problem in the past.
The way I dealt with this is defining an empty class in the toolbox. You can set and get class attributes for this class in the tool methods.
class Toolbox:
def __init__(self):
self.label = "Toolbox"
self.alias = "toolbox"
self.tools = [Tool]
class ToolboxData:
pass
class Tool:
def __init__(self):
self.label = "Tool"
self.canRunInBackground = False
def getParameterInfo(self):
version_param = arcpy.Parameter(
displayName="Version",
name="version",
datatype="GPString",
parameterType="Optional",
direction="Input")
version_param.value = "testing, testing, 123 ..."
ToolboxData.version = 100
return [version_param]
def execute(self, parameters, messages):
arcpy.AddMessage("execute: " + str(ToolboxData.version))
Thanks to both Jeff and Johannes for responding to my questions. All their responses to my post were informative and useful.
In the end, I went with Johannes suggestion because it seemed the least complicated and most easily understood by anyone who has to maintain the tool I am creating later.
Thank you!
jtm