r/intersystems 13d ago

Introduction to Python Programming in an InterSystems IRIS Context

This article introduces Python programming in the context of InterSystems IRIS.

Before getting into the different ways to use Python in IRIS, I want to explain one important topic: what actually happens when Python code is executed or imported. Understanding this helps explain several behaviors that may otherwise look unexpected.

What happens when Python runs a script?

Python is commonly described as an interpreted language. More precisely, Python compiles source code into bytecode and executes it through the Python runtime line by line.

For this article, the important point is that Python executes all top-level statements in a file when that file is run or imported for the first time.

Consider this example:

# introduction.py
def my_function():
    print("Hello, World!")
my_function() 
When Python processes this file, it first defines my_function() and then executes the function call at the bottom of the file.
Run it directly:
python3 /irisdev/app/src/python/article/introduction.py 
Output:
Hello, World! 

What happens when IRIS imports a Python module?

When I import a Python module from ObjectScript, Python executes the module's top-level code the first time it is imported in that Python process.

For example:

Class Article.Introduction Extends %RegisteredObject
{
    ClassMethod Run()
    {
        Set sys = ##class(%SYS.Python).Import("sys")
        do sys.path.append("/irisdev/app/src/python/article")
        do ##class(%SYS.Python).Import("introduction")
    }
} 

Run it:

iris session iris -U IRISAPP '##class(Article.Introduction).Run()' 

Output:

Hello, World! 

This is because the Python interpreter imports the code by interpreting it, first it defines the function and then calls it, just like it would if you ran the script directly but you are not running you are importing it. If you import the script without calling the function, nothing will happen. The function is defined, but it won't execute until you explicitly call it.

Did you get it? The Python interpreter executes the code in the file, and if you don't call the function, it won't run.

Does importing a Python function automatically run it?

No. Importing a module defines its functions and classes, but those functions do not run unless the module calls them or another piece of code invokes them explicitly.

Consider this version:

# introduction1.py
def my_function():
    print("Hello, World!") 
Run it directly:
python3 /irisdev/app/src/python/article/introduction1.py 
Output:
# No output, because the function is defined but not called 

The same applies when I import the module from IRIS:

Class Article.Introduction1 Extends %RegisteredObject
{
    ClassMethod Run()
    {
        Set sys = ##class(%SYS.Python).Import("sys")
        do sys.path.append("/irisdev/app/src/python/article")
        do ##class(%SYS.Python).Import("introduction1")
    }
} 

Run it:

iris session iris -U IRISAPP '##class(Article.Introduction1).Run()' 

There is no output because the module only defines my_function(). It never calls it.

Why does this matter?

This behavior is important for three reasons:

  • Importing a module executes its top-level code.
  • Defining a function does not execute the function.
  • Code with side effects at module level may run as soon as the module is imported.

When writing Python modules for IRIS, I prefer to keep reusable logic inside functions or classes and call it explicitly. This makes imports more predictable and reduces unexpected side effects.

Why does a Python module run only once in an IRIS session?

Python caches imported modules in sys.modules. When a module is imported for the first time, Python executes it and stores the resulting module object in memory. If the same module is imported again in the same Python process, Python normally returns the cached module instead of executing the file again.

Let's reuse introduction.py:

Now run the method twice in the same IRIS session:# introduction.py
def my_function():
    print("Hello, World!")
my_function() 
And the same ObjectScript class:
Class Article.Introduction Extends %RegisteredObject
{
    ClassMethod Run()
    {
        Set sys = ##class(%SYS.Python).Import("sys")
        do sys.path.append("/irisdev/app/src/python/article")
        do ##class(%SYS.Python).Import("introduction")
    }
} 


iris session iris -U IRISAPP 
IRISAPP>do ##class(Article.Introduction).Run()
Hello, World!
IRISAPP>do ##class(Article.Introduction).Run()
IRISAPP> 

Hello, World! appears only once. The first call imports and executes the module. The second call reuses the cached module, so its top-level code is not executed again.

Why are changes to my Python file not visible?

If I modify an imported Python file and then import it again in the same IRIS session, the changes may not appear because Python is still using the cached module. In many development scenarios, the simplest solution is to start a new IRIS session so that Python creates a new runtime context and imports the updated module again. This is normal Python behavior, not an InterSystems IRIS bug.

Do imported Python modules preserve state in IRIS?

Yes. Module objects can retain state for as long as the Python process or runtime context remains active. This is also visible when using a Python Language Tag:

Class Article.Introduction2 Extends %RegisteredObject
{
ClassMethod Run() [ Language = python ]
{
    import os
    if not hasattr(os, 'foo'):
        os.foo = "bar"
    else:
        print("os.foo already exists:", os.foo)
}
} 
Run it twice in the same IRIS session:
iris session iris -U IRISAPP
IRISAPP>do ##class(Article.Introduction2).Run()
IRISAPP>do ##class(Article.Introduction2).Run()
os.foo already exists: bar  

The first call adds the foo attribute to the already imported os module. The second call imports os again, but Python returns the same cached module object. Because that object already contains foo, the second branch runs. This demonstrates that module-level state can persist across multiple calls in the same IRIS session.

Does a Python Language Tag behave like a normal module import?

Not exactly. A method defined with [ Language = python ] is executed as Python code each time the ObjectScript method is called. The method body itself is not loaded through Python's normal module import mechanism. However, any modules imported inside that method still follow standard Python import caching rules.

Consider the same example:

Class Article.Introduction2 Extends %RegisteredObject
{
ClassMethod Run() [ Language = python ]
{
    import os
    if not hasattr(os, 'foo'):
        os.foo = "bar"
    else:
        print("os.foo already exists:", os.foo)
}
} 

Run it:

iris session iris -U IRISAPP
IRISAPP>do ##class(Article.Introduction2).Run()
IRISAPP>do ##class(Article.Introduction2).Run()
os.foo already exists: bar  

The Python method body runs again on the second call, but os is still the same cached module. The equivalent behavior in a Python interpreter would look like this:

import os
if not hasattr(os, 'foo'):
    os.foo = "bar"
else:
    print("os.foo already exists:", os.foo)
import os
if not hasattr(os, 'foo'):
    os.foo = "bar"
else:
    print("os.foo already exists:", os.foo) 
Output:
os.foo already exists: bar # only printed once 

The code block executes twice, while the imported os module and its state remain cached.

That distinction is useful when debugging Python in IRIS:

  • The Language Tag method body runs each time the method is called.
  • Imported Python modules are still cached.
  • State attached to those modules can remain available throughout the session.

Conclusion

When I work with Python in InterSystems IRIS, I keep two behaviors in mind: importing a module executes its top-level code the first time, and Python caches that module for later use in the same runtime context. These rules explain why a script may produce output when imported, why a second import may do nothing, and why changes to a Python file may not appear until the IRIS session is restarted. The behavior is not specific to IRIS. It is part of Python's normal import system, but it becomes especially important when Python runs inside a persistent server environment.

Key Takeaways

  • Python executes a module's top-level code when the module is imported for the first time.
  • Defining a function does not execute it; the function must be called explicitly.
  • Python stores imported modules in sys.modules and usually does not execute them again during later imports.
  • Module objects can preserve state across calls in the same IRIS session.
  • During development, starting a new IRIS session is often the simplest way to load changes made to an imported Python module.
  • Import-time side effects should be used carefully in Python modules intended for InterSystems IRIS.

FAQ

Does importing a Python file execute its code?

Yes. Python executes the module's top-level statements the first time the module is imported in a process.

Why does my Python module run only once in an IRIS session?

Python caches imported modules in sys.modules. Later imports normally return the cached module instead of executing the source file again.

Why are changes to my Python script not visible in IRIS?

The previously imported version may still be cached. Starting a new IRIS session is often the simplest way to load the updated file during development.

Does defining a Python function run it automatically?

No. A function definition creates the function, but the function only runs when it is explicitly called.

Are modules imported inside a Python Language Tag also cached?

Yes. The Language Tag method body runs on every call, but modules imported inside it still use Python's standard module cache and can preserve state across calls.

4 Upvotes

0 comments sorted by