Using Python with Panther

Table of Contents

.. Audience Note:
This guide is intended for developers with strong experience in both Panther and Python.
It covers advanced integration techniques and assumes prior knowledge of Panther's screen architecture and Python scripting practices.

Overview

Panther offers multiple ways to incorporate Python into your application:

  1. Screen Script — A Python Script property is available for each screen via the Properties Window in the Screen Editor.
  2. Application Script — Applications may provide appinit.py, a startup script, executed at application launch, placed in SMPATH or within a Panther library.

Panther also provides built-in Python classes that mirror core application objects (App, Screen, Field, etc.). Additionally, Python functions can be used as entry, exit, and validation functions, as well as in control strings and JPL.

Loading the Python Interpreter

Python support is enabled by default in Panther 5.60 for Panther Client. Python is not yet supported for Panther Web Application Broker or for Panther multithreaded servers, such as the Panther Application Servers for COM, JakartaEE (EJBs), JCO, JetNet, and Tuxedo.

Where supported, the interpreter is dynamically loaded using the SMPYTHONLIBRARY environment variable. This variable must point to the shared library for a supported Python 3.x interpreter (version 3.11 or later). The assignment can be made within the Environment section of the INI file, or externally within the operating system for Panther Client.

Panther loads and unloads the interpreter via calls to sm_py_init() and sm_py_finalize(), included in the provided jmain.c and jxmain.c. These calls are conditionally compiled based on the SM_PYTHON macro, which is defined by default in the end‑user makefile provided in the link folder.

Python support can be disabled by commenting out lines in the Panther Python Parameters section of the end‑user makefile and rebuilding the affected executables. For 64‑bit Panther for Windows, for example, comment out the uncommented lines shown below:

#-----------------------------------------------------------------------#
#                       PANTHER PYTHON PARAMETERS                       #
#                                                                       #
# If you do not wish to use Panther Python features, comment the        #
# lines below.  Requires Visual Studio 2022 or later.                   #
#-----------------------------------------------------------------------#

PYTHONCFLAGS    = -DSM_PYTHON=1
PYLIB           = "$(SMBASE)\lib\libpyw64.lib" "$(SMBASE)\lib\lmgr325c.lib"

Regardless of whether Python support is compiled and linked in, if SMPYTHONLIBRARY is not defined at runtime, the interpreter will not be loaded. This is handled silently with no error or warning, and the application runs without executing any Python code that may be assigned to screens.

Platform Notes

The Linux makefile contains a similar Python Parameters section, with platform‑specific variables. Commenting or uncommenting lines controls whether Python support is linked, just as on Windows:

#-----------------------------------------------------------------------#
#                       PROLIFICS PYTHON PARAMETERS                     #
#                                                                       #
# If you do not wish to use PROLIFICS Python features, comment the      #
# lines below.                                                          #
#-----------------------------------------------------------------------#
PYCFLAGS   = -DSM_PYTHON=1
PYLIB      = $(SMBASE)/lib/libpy.a
PYLMLIB    = $(SMBASE)/lib/libpylm.a
PYLDFLAGS  = $(PYTHON_LDFLAGS)

Licensing Notes

Beginning with Panther 5.60, use of the Python feature in the runtime executable (prorun/prorun64.exe/prorun32.exe) requires a FlexLM license that includes the prolifics-python feature line. This is the first time prorun has required a license for any feature. By contrast, the Python feature can be used in the development executable (prodev/prodev64.exe/prodev32.exe) with an existing development license, even if that license does not explicitly list prolifics-python.

If a runtime license is not used, or if it does not include the prolifics-python feature, prorun will silently ignore any Python scripting. The application otherwise runs normally, consistent with the behavior when SMPYTHONLIBRARY is not set.

Note: Panther Web Application Broker and Panther Web Application Server have long required licenses. The requirement for prorun is new and applies only when Python support is used in deployed applications.

Python Property for Screens

In the Screen Editor, each screen has a Python Script property under the FOCUS section. Selecting this opens a non-modal editor to input Python code associated with the screen. The Editor button launches an external editor, which may be specified using the SMPYEDITOR environment variable. If SMPYEDITOR is not provided, the value for SMEDITOR will be used instead, the same as for JPL. In Windows, SMPYEDITOR may also be set in the Environment section of the INI file.

Note that SMPYEDITOR is not used when double-clicking on a Python source file within the Library Table of Contents screen. In that case, the Editor will open whatever application is associated with the .py filename extension.

The following criteria apply to the Python Script assigned to a screen:

The editor performs a basic syntax check during save, but many Python errors can only be detected at runtime. If there is a compilation error, the built-in editor repositions the caret cursor to the start of the line containing the error. It also shows the current line number on the status bar when editing.

If the interpreter isn't available at design time, the editor allows saving Python code anyway—without performing syntax validation.

Encoding Caveat for Embedded Python Scripts

Panther expects Python scripts to be encoded in UTF-8, which aligns with the encoding requirements of the Python interpreter. While the built-in editor fully accepts UTF-8 input — including multibyte characters — it does not render all characters correctly if their UTF-8 byte sequences do not map cleanly to glyphs in the current system code page.

As a result, certain international characters may appear distorted, display incorrect glyphs, or render as multiple glyphs per character. This is a display limitation only: the underlying UTF-8 data remains intact.

Scripts entered using the built-in editor will be correctly saved in UTF-8 and can be reliably imported and exported to external editors that support UTF-8, such as Visual Studio Code or Notepad. You can also copy and paste UTF-8 content between Panther and these editors without loss of fidelity. You may may even enable Options->Direct to External Editor on the menubar, in order to bypass the built-in editor, though this affects JPL editing as well.

⚠️ Important for Internationalization: If your script contains only a few international characters outside the standard ASCII range (code points > 127), you should escape them within string literals using Unicode escape sequences (e.g., \u00E9 for é). This ensures the script remains readable and portable across different encodings and platforms, regardless of how the editor displays those characters.

Alternatively, you can save the full Python code in a separate UTF-8 encoded file and embed a single line in the Panther screen to import it:

import my_script

Using VS Code as the External Python Editor

To use Visual Studio Code as the external editor in Windows, for example, set SMPYEDITOR or SMEDITOR to:

<path-to-VS Code>\Code.exe --wait %s
or
<path-to-VS Code>\Code.exe --new-window --wait %s

The first option will use an open VS Code IDE or open a new one if necessary. The second option uses the --new-window flag to force a new VS Code IDE to always be opened. In both cases, a new tab will be created in VS Code for editing a Python temp file provided by Prodev. Any changes made must be saved to that temp file, in order for Prodev to receive them. The --wait flag is always required. Access to Prodev is blocked when this flag is used, enabling Prodev to read back in the temp file only when you are done working with it externally.

Closing only the VS Code tab containing the temp file is sometimes insufficient for Prodev to continue. Closing all tabs and the IDE may be necessary, depending upon how VS Code happens to be managing its open tabs, files, and processes. If you find that you are having to close existing tabs for other work while using the --wait flag alone, consider using it with the --new-window flag, as shown.

Visual Studio Code provides other command-line options that can be used. Refer to Visual Studio Code documentation for further information.

Application Initialization Script

An optional application-provided initialization script named appinit.py is compiled and exec’d by Panther’s Python framework within an instance method of the Appinstance. This occurs at the end of the initialization performed by the C function, sm_py_init(). The call to this function comes before any screen is opened, and is located in C source code that is provided for application customization (e.g., jxmain.c, jmain.c).

appinit.py may be used for:

Within this script, self is a reference to the singleton instance of the Appclass (a.k.a., _app). As such, Python variables defined within appinit.py are not externally accessible as Python attributes of _app using dot-syntax, since dot-syntax is used almost exclusively by Panther’s proxy classes for Panther Properties. Since appinit.py is not imported when Panther executes its code, one cannot get at its variables from a screen’s Python Script by using ‘import appinit’, as this will re-execute the script’s top-level code. One can, however, import another custom module from within appinit.py, in which application state variables are defined. A screen’s Python Script can then import that module to gain access to its state variables.

Alternatively, variables defined in appinit.py can be accessed by way of the execution context dictionary that Panther assigns as the _exec_context attribute of _app.

Code in appinit.py:
myvar = "Hello, World"
Screen Python Script:
context = _app._exec_context
print(context["myvar"])  # prints "Hello, World"

Note: Imported modules and their variables persist, except when toggling between Test Mode and Edit Mode in Prodev. Therefore, the values assigned to those variables persist, even when those module names and variable names are out of scope.

An application need not provide appinit.py if no Python start-up code is desired. Panther provides a default implementation for this file that imports smprintbx.py, but does nothing more.

Hook Functions in Python

Python functions within a screen and appinit.py are automatically registered for foreign function invocation by Panther, provided they meet specific criteria:

Python functions that are registered for Panther invocation behave like C hook functions that are installed as Prototyped Functions:

When the same function name is implemented in different technologies, Panther only ever calls one of them, the one that has the highest precedence.

It is best to catch and handle exceptions in your Python code. If you allow an exception to propagate out of the Python screen code when it is first executed, its functions will not be available as Panther hook functions. This is true even if a portion of your code that defines them has been executed prior to the exception.

While Python hook functions receive a self parameter referencing the screen, whether defined as a parameter or not, they are true Python functions—not methods. For other arguments, types are mapped as needed between technologies.

⚠️ Limitations

Panther executes Python code in a non-standard context that differs from typical Python applications. This can lead to unexpected behavior if you're not aware of the underlying mechanics. Here’s what you need to know:

🧠 Non-Standard Execution Context

When Panther calls a Python function, it does so using exec() with a custom global dictionary — not the module’s native namespace. This dictionary includes Panther-injected globals such as screen widgets and library functions, and it changes dynamically as screens are opened and closed.

🚫 Common Limitations and Surprises

These limitations stem from Panther’s integration model, which prioritizes dynamic screen-based execution over conventional Python module semantics. By designing your functions with this in mind, you’ll avoid common pitfalls and ensure smoother behavior.

Type Hint Requirements for Function Registration

Python functions are registered by Panther in the same manner as C Prototyped functions, according to a mapping of Python type hints to Panther prototypes. In the table that follows, the prototype for a particular Parameter Type Hint is either an s or an i among, potentially, others in a coma-separated list for multiple parameters. For example, Panther's internal Prototyped Function List for its C functions contains the following entry for sm_i_fptr:

SM_STRFNC ("sm_i_fptr(s,i)", sm_i_fptr),

This is the same format as one uses to specify custom C Prototyped Functions in funclist.c.

If it were a Python function, it would be defined with type hints like this:

def sm_i_fptr(field_name:str, occurrence:int)->str:

Whether called from JPL or otherwise, Panther calls Python hook functions from C internally. In doing so, a char* is conveted to a str and an int to an int. For return values, a str is converted to a char*, an int to an int, a double to a float, and None to a 0. Values that don't match the type hint typically result in a TypeError being raised, but may be converted to the required type if reasonable.

Parameter Type Hint to C Prototype and Python Conversion
Parameter Type Hint Parameter Prototype Python Conversion Notes
:str s str(arg)
:int i int(arg)
missing hint - - not registered
other than :int or :str - - not registered
Return Type Hint to Python Conversion and C Prototype
Return Type Hint Python Conversion Return Prototype Notes
->str str(arg) SM_STRFNC
->int int(arg) SM_INTFNC
->float float(arg) SM_DBLFNC
->None None SM_ZROFNC
missing hint int(arg) SM_INTFNC Defaults to int return type
other than the above - - not registered

Below are some examples that should help to explain how to define Python hook functions so that they are callable from Panther (e.g. JPL) with the desired argument types and return types:

# No type hint - default is int function; returns 2
def myFunc1():
        return 2

# No type hint - returns 2, converting return string to an int
def myFunc2():
        return "2"

# No type hint - returns 2, converting return float to an int
def myFunc3():
        return 2.6

# No type hint - raises and displays a TypeError; returns -1
def myFunc4():
        return "Hello"

# returns arg1, an int
def myFunc5(arg1:int, arg2:str="Default"):
        return arg1

# returns 0, same as a built-in C functions that return void
def myNoneFunc1()->None:
        pass

# returns 0; return statement's value is ignored
def myNoneFunc2()->None:
        return 2

# returns 2
def myIntFunc()->int:
        return 2

# returns arg as an int
def myIntFunc(arg:int)->int:
        return arg

# returns arg2 as a string; returns "default" if only arg1 is passed in
def myIntStrFunc(arg1:int, arg2:str="default")->str:
        return arg2

# missing type hint for arg1; not registered - not Panther-callable
def myBadFunc1(arg1):
        return arg1

# missing type hint for arg2; not registered - not Panther-callable
def myBadFunc2(arg1:int, arg2)->int:
        return arg1

When prototyped C functions, such as Panther Library Functions, are called from Python code, their protoypes determine type conversions in much the same manner.

External Python Modules

When Python import statements are used, Panther resolves module names using a custom loader. The search order is:

  1. Panther libraries
  2. Current Working Directory
  3. SMPATH directories
  4. Standard Python module paths (as fallback)

The same search order is used in locating appinit.py, except that there is no fallback to standard Python module paths.

This mechanism allows you to package Python source files directly within Panther libraries, or outside of them, alongside screens, JPL, and other Panther application files. Precompiled .pyc files are also supported but discouraged due to potential compatibility issues with future Python interpreter versions.

Modules that are custom loaded from any of the three special locations in the search order have special ways in which they can be used. Their functions are registered using their full module names, and must be referenced that way in control strings and other Panther expressions. For example, to call a Python method that is within an imported myscreen module from a control string, use ^myscreen.myfunc(). If myfunc is defined directly within the Python screen code, then ^myfunc() should be used instead.

The names of widgets on the current screen are global during execution of the top level script within custom loaded modules. The Screen instance is also available as the self variable, the same as for Python code embedded within the screen itself. This makes it easy to move code that is embedded within a screen into an imported module, provided that such code is to be executed only once. Python ‘import’ statements are like JPL ‘public’ statements, not JPL ‘include’ statements.

Built-in Python Classes

Panther provides several built-in Python classes to simplify scripting:

The Field class, in particular, extends both Widget and Array. Array itself extends collections.abc.MutableSequence.

Occurrence access is 1-based, the same as in JPL. Default iteration is from 1 through the highest allocated occurrence, which will include any trailing empty occurrences. The len() method, however, returns the highest numbered non-empty occurrence.

The class hierarchy for Field looks like this:

Field → Array → MutableSequence
      ↘ Widget (1st in the MRO)

This dual inheritance allows Python code to treat Panther fields both as widgets and as arrays of values.

Accessing Field Data and Properties

To retrieve or set the first occurrence’s value:

myfield.value = "New Value"
value = myfield.value

‘value’ is a pseudo-property of Field, provided for convenience. This is equivalent to:

myfield[1] = "New Value"
value = myfield[1]

The Field class overrides the default __str__() method to return the value within a field as a string. Wherever Python will automatically convert a Field instance into a str, it will use the value within that field. For example:

myfield.value = "String Value"
value = f"{myfield}"  # assigns "String Value" to the variable

A field’s c_type and/or its data formatting property is used in determining which Python value types to accept or to retrieve. The default is to assign and retrieve a str value. A field should have a c_type of int, for example, when assigning a Python int:

count_field.value = 10

A field with a data_formatting property of numeric and a format_type of local_currency:

amount_field.value = 10.202

Panther displays
$10.20

To retrieve a widget reference by name or field number:

field_ref = self["total$"]
field_ref = self["#2"]  # Field number

Field names that contain characters like $, and unnamed fields, can be referenced only by field number, using Panther’s ‘#’ operator. They must be accessed using self[...] syntax. Direct reference by name is available only if the name is a valid Python identifier:

field_ref = customer_id

Panther array field occurrences may be accessed using a variety of list-style syntaxes:

myfield[1] = "John"           # Set the first occurrence to "John"
name = myfield[2]              # Retrieve the second occurrence from myfield
del myfield[3]                 # Remove the third occurrence
myfield[4:7] = ["A", "B", "C"] # Replace occurrences 4,5,6 with new values
values = myfield[1:5]          # Get occurrences 1 through 4
del myfield[2:4]               # Delete occurrences 2 and 3
myfield[-1] = "Last"           # Update last occurrence using negative index
entry = myfield[-2]            # Access the second-to-last occurrence
del myfield[-3]                # Remove the third-to-last occurrence
myfield.append("New Entry")    # Add a new occurrence to the end
count = len(myfield)           # Get the number of occurrences of myfield
value = myfield.pop()          # Remove and return last item
myfield.remove("old")          # Remove first occurrence of "old"
myfield.reverse()              # Reverse the order of items
myfield.reverse(len(myfield)+1)# non-standard reverse with custom end
reversed = myfield[::-1]       # assign a reverse slice
myfield.sort()                 # Uses sm_obj_sort()- key arg not supported
myfield.clear()                # Uses sm_1clear_array()
i = myfield.index("John")      # assign occurrence number of 1st "John" value

Some Array methods are implemented in Python rather than in C and use Python iteration and individual calls to a Panther API for each index. These methods should not be expected to be as performant as native Python list methods. Also, default iteration extends to the last allocated slot in an array. In other words, when looping through myfield (e.g., for item in myfield:), when myfield contains empty trailing occurrences, iteration goes beyond len(myfield). Use a slice on myfield if that isn’t desired (e.g., for item in myfield[1:len(myfield)]:).

Some Panther widgets are not fields. A Static Label widget, for example, is represented as an instance of Panther’s Widget class. A Panther Box, Grid Frame, Sync Group, Selection Group, Tab Deck, Tab Card, or Table View is represented as an instance of the ContainerWidget class, which is an extension of both Widget and PantherContainer. The Screen class also extends PantherContainer. Objects inheriting from PantherContainer can be traversed using standard Python coding techniques for traversing the members of a Python dictionary. The keys are Panther object IDs, and the values are instances of the various classes already discussed.

The following code prints the names of the fields within a Grid Frame widget named mygrid:

for field in mygrid.values():
    print(field.name)
Non-field Panther Widget to Python Class Mapping
Panther Widget Python Class Notes
Static Label Widget label property and others, but no value; no indexing
Horizontal Line Widget property access, but no indexing
Vertictal Line Widget Same as above
Link Widget Same as above
ActiveX Widget Same as above
Graph Widget Same as above
Box ContainerWidget Inherits from Widget and PantherContainer
Grid Frame ContainerWidget Same as above
Sync Group ContainerWidget Same as above
Selection Group ContainerWidget Same as above
Tab Deck ContainerWidget Same as above
Tab Card ContainerWidget Same as above
Table View ContainerWidget Same as above

Working with Widget and Field Properties

All Panther widget and field properties are exposed using Python’s dot-syntax:

submit_button.label = "Submit"
submit_button.enabled = False

Property values are type-converted upon assignment and retrieval, based upon the apparent types for their values, since Panther provides no data-type information for its properties. Implicit data-type conversion works with str, int or float.

To set a field’s default foreground color:

myfield.fg_color_type = PV_BASIC
myfield.fg_color_num = GREEN

This establishes a default appearance across all occurrences. Some properties, such as this one, can be overridden by element and/or occurrence number for more granular control. For example:

Properties(myfield)[1].fg_color_num = GREEN

This sets the foreground color for the first occurrence only. The fg_color_num property of element 1 can also be assigned:

ElementProperties(myfield)[1].fg_color_num = GREEN

Slices for Properties and ElementProperties are not supported.

Practical Python Examples

# Test if running in Panther Web Application Broker:
if _app.in_web:

# Set a screen title:
self.title = "My Screen"

# Access field data:
customer_id.value = "12345"
order_total[1] = 99.95

# Set widget properties at runtime:
submit_button.enabled = True
status_label.value = "Complete"

# Assign a pushbutton’s control string dynamically:
submit_button.control_string = "^submit_form()"

# Set the length of all fields within a Grid Frame widget to 5:
for fld in mygrid.values():
    fld.length = 5

# Add up the values in a field named price, and assign the total to a field
# named total:
total.value = sum(price)

# Add up the total, assigning discounts to the prices in the price column of
# the price_table Grid Frame widget, using discount percentages from the
# discount column in the same Grid Frame:
total.value = 0
for price, discount in zip(price_table['price'](1,len(price)), price_table['discount']):
    discounted_price = price * (1 - discount / 100)
    total.value += discounted_price

SQL Support

Python code does not support embedded SQL as in JPL. Instead, use Panther’s dm_* Library functions:

sql = f"select * from customers where id = {customer_id.value}"
dm_exec_sql(sql)

Be sure the SQL string uses syntax that Panther can parse (e.g., :+fieldname expansion). Results cannot be placed directly into Python variables—only into Panther fields.

Built-in Python Modules

Panther includes several Python modules to support its Python integration. These are located in two places:

Internationalization

Both standard Python and standard Panther techniques for internationalization can be used by application code. Additionally, when assigning strings to fields and properties, and when passing strings to prototyped C functions, Panther will automatically convert Python strings using the single-byte-per-character encoding specified by the PR_CODESET application property. It will do the reverse when retrieving string values from fields and properties, and when calling Python hook functions. Be aware that some runtime errors that are reported by Panther’s Python framework do not come from Panther’s message file, and cannot be internationalized in a standard way.

⚠️ Important for Internationalization Panther automatically sets the PR_CODESET property based on locale. It will choose a single-byte-per-character codeset for Windows, but for Linux make sure that UTF-8 does not appear in the encoding portion of your LANG environment variable. Choose a single-byte-per-character encoding for LANG instead. (e.g., LANG=en_US.ISO-8859-1) Panther will not display multibyte UTF-8 characters properly. Any that appear in string constants or the names of identifiers will likely lead to application processing errors.

Debugging

Standard Python debugging techniques can be used with Panther. To enable debugging, add the following code directly within appinit.py:


import sys
sys.executable = r"C:\Python311\python.exe"
import debugpy
debugpy.listen(("localhost", 9092))  # Port can be any open one
debugpy.wait_for_client()            # Waits until VS Code connects
# debugpy.breakpoint() can be used to drop into the debugger,
# but avoid placing it as the last line in appinit.py.

VS Code Setup

To debug using Microsoft Visual Studio Code:

  1. Ensure the Python Debugger extension is installed.
  2. Create or edit .vscode/launch.json in your workspace folder. Add a configuration for attaching to Panther:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Attach to Panther",
      "type": "debugpy",
      "request": "attach",
      "connect": {
        "host": "localhost",
        "port": 9092
      }
    }
  ]
}

The port number (9092 in this example) must match the one used in debugpy.listen() inside appinit.py.

Preparing the Workspace

For breakpoints to bind reliably, make sure your current working directory for Panther is also the folder you open as your VS Code workspace. Save copies of embedded screen scripts in this directory using the following filename rule:

Connecting the Debugger

After setting up the workspace and modifying appinit.py, start Panther. The application will appear as “Not Responding” while it waits for VS Code to connect. If you do not connect, you will need to terminate Panther in Task Manager, unless you implement a timeout mechanism around debugpy.listen().

In VS Code:

You may see warnings about frozen modules (precompiled system classes introduced in Python 3.11+). These can be safely ignored, since you typically don’t need to debug system code.

Development Workflow

Key takeaway: Breakpoints work reliably when the saved copies of embedded screen scripts (*.jam.py) are located in the current working directory, and that directory is also opened as your VS Code workspace.

f2asc Enhancements

ASCII screen files now support embedded Python code using PYTEXT= directives. Converting back to binary does not compile the Python code, which may result in invalid syntax being preserved in the binary screen file.

Q&A

❓ Why is the screen object called self? Why not screen?
🟢 Answer: In Python, self is equivalent to this in languages like Java or C++. It refers to the current instance of the class. While self is just a convention and any name could technically be used, Panther scripts act as methods of the Screen instance. Since there's no place to define parameters for these scripts, Panther automatically defines a self variable to reference the current screen instance.

❓ If the script is like a method for the Screen instance, why do functions in the script have access to self without being defined with a self parameter?
🟢 Answer: Functions in the screen's Python script behave like nested functions inside an unnamed outer function (the script itself). As nested functions, they inherit access to variables like self defined in the enclosing scope.

❓ When I execute:
Properties(myfield)[1].fg_color_num = GREEN
ElementProperties(myfield)[1].fg_color_num = BLUE

...my widget still shows the green foreground color. Why?
🟢 Answer: The occurrence property (Properties) takes precedence over the element property (ElementProperties). That’s why the green color remains visible.

❓ What does this do?
Properties(myfield)[0].fg_color_num = GREEN
🟢 Answer: This behaves like myfield.fg_color_num = GREEN, but it is undocumented behavior and may change. Panther allows myfield[0] to reference the base field in non-Python code (e.g., in JPL), and this leniency was intentional to accommodate common coding errors.

❓ Why do dictionary keys for a Screen or ContainerWidget appear as Panther object IDs when looping, but square-bracket notation uses widget names?
🟢 Answer: Panther uses object IDs as dictionary keys because every widget has one, even unnamed ones. However, since object IDs vary between runs, Panther overrides __getitem__ and __setitem__ to allow referencing widgets by name, field number, or object ID using square brackets. For example:
myfield['#2']

❓ Why are functions in appinit.py global, but variables are not?
🟢 Answer: Functions need to be callable from Panther outside Python, while variables are only used within Python code. You can still access both by importing appinit.py, but only functions are made global by design.

❓ Why can't I assign a value to a field like in JPL (e.g., myfield = "Hello")?
🟢 Answer: In Python, myfield is an instance of the Field class. Assigning "Hello" to it simply rebinds the variable to a string, not the field’s value. Python doesn’t allow overriding assignment behavior, so Panther can’t intercept that to update the field internally.

❓ Can I create a Field instance using its constructor?
🟢 Answer: Yes, but it's rarely necessary. You can do:
myfield_copy = Field(myfield.objid)
Or make a shallow copy:
myfield_copy = copy.copy(myfield)
These create new Python objects referencing the same Panther widget, not new widgets. Alternatively, you can just create a new reference:
myfield_ref = myfield

❓ How can I reference @-variables like @dmrowcount?
🟢 Answer: Use Panther’s API functions:
• For integers:
sm_n_intval('@dmrowcount')
• For strings:
sm_n_fptr('@dmengerrmsg')
These work with screen, widget, and property references, as well as with globals—but not with @-functions like @sum(). Panther’s @-functions can be called from Python using sm_calc():
• For functions:
sm_calc(0, 0, 'total = @sum(numbers)')
total and numbers must be fields or global JPL variables.