IRIS環境でのPythonプログラミング入門

この記事では、IRIS環境におけるPythonプログラミングの基礎について紹介します。
本題に入る前に、重要なトピックである「Pythonの仕組み」について説明します。これは、IRIS環境でPythonを使用して作業する際に起こりうる問題や制限を理解するのに役立ちます。
すべての記事と例は、以下のgitリポジトリで確認できます: iris-python-article
Pythonの仕組み
インタープリター型言語
Pythonはインタープリター型言語であり、コードはランタイム時に1行ずつ実行されます。スクリプトをインポートする場合でも同様です。
これはどういうことでしょうか? 以下のコードを見てみましょう。
# introduction.py
def my_function():
print("Hello, World!")
my_function()
このスクリプトを実行すると、Pythonインタープリターはコードを1行ずつ読み取ります。 まず最初に関数 my_function を定義してから、その関数を呼び出すと、コンソールに「Hello, World!」と出力されます。
スクリプトを直接実行している例:
python3 /irisdev/app/src/python/article/introduction.py
出力は以下のようになります。
Hello, World!
IRIS環境でこのスクリプトをインポートするとどうなるのでしょうか?
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 '##class(Article.Introduction).Run()'
出力が表示されます。
Hello, World!
これは、Pythonインタープリターがコードを解釈しながらインポートするためで、最初に関数を定義し、その後に関数を呼び出します。これはスクリプトを直接実行した場合と同じ動作ですが、実行しているのではなくインポートしています。
⚠️ 重要な注意事項:関数を呼び出さずにスクリプトをインポートしても、何も起こりません。 関数は定義されますが、明示的に呼び出さない限り実行されません。
分かりましたか? Pythonインタープリターはファイル内のコードを実行しますが、関数を呼び出さなければ、その関数は実行されません。
呼び出さずにインポートする例:
# introduction1.py
def my_function():
print("Hello, World!")
Pythonインタープリターで実行しましょう。
python3 /irisdev/app/src/python/article/introduction1.py
出力:
# 関数は定義されていますが、呼び出されていないため、出力はありません
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")
}
}
実行しましょう。
iris session iris -U IRISAPP '##class(Article.Introduction1).Run()'
関数は定義されていますが、呼び出されていないため、出力はありません。
🤯この微妙な違いが重要な理由
- Pythonスクリプトをインポートすると、そのスクリプトのコードが実行されます。
- コードを実行したくない場合があります
- インポートするとスクリプトが実行されているように見えるかもしれませんが、実際には直接実行されているわけではないため、混乱を招く可能性があります。
インポートのキャッシュ
Pythonスクリプトをインポートすると、Pythonインタープリターがインポートされたスクリプトをキャッシュします。 つまり、同じスクリプトをもう一度インポートすると、そのスクリプトのコードは再実行されず、キャッシュされたバージョンが使用されます。
具体例による説明:
introduction.py スクリプトを再利用しましょう。
# introduction.py
def my_function():
print("Hello, World!")
my_function()
次に、同じように Article.Introduction クラスを再利用しましょう。
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セッション内で2回続けて実行します。
iris session iris -U IRISAPP
IRISAPP>do ##class(Article.Introduction).Run()
Hello, World!
IRISAPP>do ##class(Article.Introduction).Run()
IRISAPP>
🤯一体どういうことでしょうか?
はい、「Hello, World!」は一度だけ出力されます!
⚠️ インポートされたスクリプトはキャッシュされています。 つまり、インポートした後にスクリプトを変更しても、IRSセッションが変更されるまで変更は反映されません。
IRISで 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)
}
}
実行しましょう。
iris session iris -U IRISAPP
IRISAPP>do ##class(Article.Introduction2).Run()
IRISAPP>do ##class(Article.Introduction2).Run()
os.foo already exists: bar
なんと、os モジュールはキャッシュされ、foo 属性は存在しないことに再定義されていません。
まとめ
この入門編が、IRISでPythonを使用する際に、特にスクリプトのインポートやキャッシュ処理に関して、予期しない動作が発生する理由を理解する助けになれば幸いです。
IRISでPythonを使う際のポイント:
- Pythonスクリプトで変更を確認するには、IRISセッションを毎回変更する。
- これはバグではなく、Pythonの仕様です。
- スクリプトをインポートするとそのコードが実行されることに気を付ける。
ボーナス
待って! スクリプトをインポートするとキャッシュされる?つじつまが合いません。 language tag = python で作業していて、スクリプトを変更したのにIRISセッションを変更しなくてもうまく動作するのは何故でしょうか?
いい質問です。これは language tag の仕組みが関係しています。language tag は実行するたびにスクリプトをもう一度読み込み、ネイティブのPythonインタープリターで新しい行を入力するかのように、行ごとに実行します。language tag はスクリプトをインポートするわけではなく、Pythonインタープリターを再起動せずに直接スクリプトを実行しているのと同じ動作をします。
例:
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)
}
}
実行しましょう。
iris session iris -U IRISAPP
IRISAPP>do ##class(Article.Introduction2).Run()
IRISAPP>do ##class(Article.Introduction2).Run()
os.foo already exists: bar
Pythonインタープリターだと、こんな感じになります。
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)
出力:
os.foo already exists: bar # only printed once
いかがでしょうか。
今後の内容 :
- Pep8
- モジュール
- ダンダーメソッド
- IRISでPythonを動かす
- ...