ObjectScript と組み込み Python 間のギャップを埋める
ObjectScript と Python の言語には違いがあるため、その言語間のギャップを埋めるのに知っておく必要のある情報があります。
ObjectScript 側では、%SYS.PythonOpens in a new tab クラスにより、ObjectScript から Python を使用できるようになります。詳細は、InterSystems IRIS のクラス・リファレンスを参照してください。
Python 側では、iris モジュールにより、Python から ObjectScript を使用できるようになります。Python から、モジュールと関数のリストを表示するには「help(iris)」と入力します。詳細は、"InterSystems IRIS Python モジュール・リファレンス" を参照してください。
Python の組み込み関数の使用
builtins パッケージは Python インタプリタの起動時に自動的にロードされます。このパッケージには、ベース・オブジェクト・クラスや、すべての組み込みのデータ型クラス、例外クラス、関数、および定数など、この言語に組み込まれたすべての識別子が含まれます。
これらの識別子すべてにアクセスできるようにするには、以下のようにこのパッケージを ObjectScript にインポートします。
set builtins = ##class(%SYS.Python).Import("builtins")
Python の print() 関数は、実際には builtins モジュールのメソッドであるため、この関数を以下のように ObjectScript から使用できるようになります。
USER>do builtins.print("hello world!")
hello world!
次に zwrite コマンドを使用して builtins オブジェクトを調べることができます。これは Python オブジェクトであるため、builtins パッケージの str() メソッドを使用して、そのオブジェクトの文字列表現を取得します。以下に例を示します。
USER>zwrite builtins
builtins=5@%SYS.Python ; <module 'builtins' (built-in)> ; <OREF>
同じトークンによって、builtins.list() メソッドを使用して Python リストを作成できます。以下の例は、空のリストを作成します。
USER>set list = builtins.list()
USER>zwrite list
list=5@%SYS.Python ; [] ; <OREF>
builtins.type() メソッドを使用して、変数 list がどの Python の型であるかを調べることができます。
USER>zwrite builtins.type(list)
3@%SYS.Python ; <class 'list'> ; <OREF>
興味深いことに、list() メソッドは、実際にはリストを表す Python のクラス・オブジェクトのインスタンスを返します。以下のようにリスト・オブジェクトで dir() メソッドを使用することで、list クラスが持つメソッドを確認できます。
USER>zwrite builtins.dir(list)
3@%SYS.Python ; ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__',
'__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__','__repr__', '__reversed__',
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append','clear',
'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ; <OREF>
同様に、help() メソッドを使用して、リスト・オブジェクトについてのヘルプを取得できます。
USER>do builtins.help(list)
Help on list object:
class list(object)
| list(iterable=(), /)
|
| Built-in mutable sequence.
|
| If no argument is given, the constructor creates a new empty list.
| The argument must be an iterable if specified.
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
|
| __delitem__(self, key, /)
| Delete self[key].
.
.
.
ObjectScript に builtins モジュールをインポートする代わりに、%SYS.PythonOpens in a new tab クラスの Builtins() メソッドを呼び出すことができます。
識別子名
識別子の命名規則は、ObjectScript と Python で異なります。例えば、アンダースコア (_) は Python のメソッド名で許可されており、実のところいわゆる “ダンダー (dunder)” メソッドおよび属性 (“dunder” は “double underscore” の省略形) 用に広く使用されています (__getitem__ や __class__ など)。ObjectScript からそのような識別子を使用するには、以下のようにそれらを二重引用符で囲みます。
USER>set mylist = builtins.list()
USER>zwrite mylist."__class__"
2@%SYS.Python ; <class list> ; <OREF>
逆に、InterSystems IRIS メソッドは多くの場合パーセント記号 (%) で始まります。例えば、%New() や %Save() のようになります。Python からそのような識別子を使用するには、パーセント記号をアンダースコアで置き換えます。永続クラス User.Person がある場合、Python コードの以下の行は、新しい Person オブジェクトを作成します。
>>> import iris
>>> p = iris.cls('User.Person')._New()
キーワードまたは名前付き引数
Python では、メソッドを定義する際にキーワード引数 (または "名前付き引数" とも呼ばれる) を使用するのが一般的な方法です。これにより、必要でない場合に引数を削除したり、場所ではなく名前に応じて引数を指定したりすることが容易になります。例として、以下の簡単な Python メソッドを見てみましょう。
def mymethod(foo=1, bar=2, baz="three"):
print(f"foo={foo}, bar={bar}, baz={baz}")
InterSystems IRIS にはキーワード引数の概念がないため、ダイナミック・オブジェクトを作成して、キーワード/値のペアを保持する必要があります。例えば、以下のようになります。
set args={ "bar": 123, "foo": "foo"}
メソッド mymethod() が mymodule.py というモジュール内にある場合、それを ObjectScript にインポートして、以下のように呼び出すことができます。
USER>set obj = ##class(%SYS.Python).Import("mymodule")
USER>set args={ "bar": 123, "foo": "foo"}
USER>do obj.mymethod(args...)
foo=foo, bar=123, baz=three
baz はメソッドに渡されていないため、既定で "three" の値が割り当てられます。
引数の参照渡し
ObjectScript で記述されたメソッドの引数は、値または参照によって渡すことができます。以下のメソッドでは、シグニチャの 2 つ目と 3 つ目の引数の前にある ByRef キーワードは、それらを参照渡ししようとしていることを示しています。
ClassMethod SandwichSwitch(bread As %String, ByRef filling1 As %String, ByRef filling2 As %String)
{
set bread = "whole wheat"
set filling1 = "almond butter"
set filling2 = "cherry preserves"
}
ObjectScript からメソッドを呼び出す際は、以下のように引数の前にピリオドを配置して、それを参照渡しします。
USER>set arg1 = "white bread"
USER>set arg2 = "peanut butter"
USER>set arg3 = "grape jelly"
USER>do ##class(User.EmbeddedPython).SandwichSwitch(arg1, .arg2, .arg3)
USER>write arg1
white bread
USER>write arg2
almond butter
USER>write arg3
cherry preserves
出力から、変数 arg1 の値が、SandwichSwitch() を呼び出した後も同じであるのに対し、変数 arg2 および arg3 の値は変化したことがわかります。
Python は参照による呼び出しをネイティブでサポートしないため、iris.ref() メソッドを使用して、参照渡しする各引数についてメソッドに渡す参照を作成する必要があります。
>>> import iris
>>> arg1 = "white bread"
>>> arg2 = iris.ref("peanut butter")
>>> arg3 = iris.ref("grape jelly")
>>> iris.cls('User.EmbeddedPython').SandwichSwitch(arg1, arg2, arg3)
>>> arg1
'white bread'
>>> arg2.value
'almond butter'
>>> arg3.value
'cherry preserves'
value プロパティを使用して arg2 と arg3 の値にアクセスし、それらがメソッドへの呼び出しの後に変化したことを確認できます。
ObjectScript には、引数が参照によって渡され、この引数が値を受け取らずに出力として使用されることが想定されることを示すキーワード Output もあります。Python から iris.ref() メソッドを使用して、ByRef 引数の場合と同じ方法で引数を渡します。
引数の参照渡しは ObjectScript メソッドの機能ですが、Python で記述されたメソッドに引数を参照渡しする同等の方法はありません。ObjectScript メソッドのシグニチャの ByRef および Output キーワードは、引数が参照渡しされることをメソッドが期待していることをユーザに示すための規則です。実際、ByRef と Output には実際の機能はなく、コンパイラからは無視されます。ByRef または Output を Python で記述されたメソッドのシグニチャに追加すると、コンパイラ・エラーが発生します。
True、False、None の値を渡す方法
%SYS.PythonOpens in a new tab クラスには True()、False()、および None() のメソッドがあり、これらはそれぞれ Python の True、False、および None の識別子を表します。
以下に例を示します。
USER>zwrite ##class(%SYS.Python).True()
2@%SYS.Python ; True ; <OREF>
これらのメソッドは、Python メソッドに True、False、および None を渡す必要がある場合に役立ちます。以下の例は、"キーワードまたは名前付き引数" で示したメソッドを使用しています。
USER>do obj.mymethod(##class(%SYS.Python).True(), ##class(%SYS.Python).False(), ##class(%SYS.Python).None())
foo=True, bar=False, baz=None
キーワード引数を期待している Python メソッドに名前のない引数を渡すと、Python はそれらを渡された順序で処理します。
Python メソッドによって ObjectScript に返される値を調べる際に True()、False()、および None() のメソッドを使用する必要がないことに注意してください。
Python モジュール mymodule に、isgreaterthan() メソッドもあるとします。これは、以下のように定義されます。
def isgreaterthan(a, b):
return a > b
Python で実行すると、このメソッドは引数 a が b よりも大きい場合に True を返し、そうでない場合に False を返すことがわかります。
>>> mymodule.isgreaterthan(5, 4)
True
しかし、ObjectScript から呼び出した場合、返される値は 1 であり、Python 識別子の True ではありません。
USER>zwrite obj.isgreaterthan(5, 4)
1
ディクショナリ
Python では、ディクショナリは一般的にキーと値のペアの形式でデータを格納するために使用されます。例えば以下のようになります。
>>> mycar = {
... "make": "Toyota",
... "model": "RAV4",
... "color": "blue"
... }
>>> print(mycar)
{'make': 'Toyota', 'model': 'RAV4', 'color': 'blue'}
>>> print(mycar["color"])
blue
メソッド iris.arrayref() を使用してディクショナリ mycar の内容を ObjectScript 配列に配置して、その配列への参照を返すことができます。
>>> a = iris.arrayref(mycar)
>>> print(a.value)
{'color': 'blue', 'make': 'Toyota', 'model': 'RAV4'}
>>> print(a.value["color"])
blue
その後、その配列を ObjectScript メソッドに渡すことができます。例えば、配列の内容を書き込むメソッド WriteContents() を持つ、User.ArrayTest という InterSystems IRIS クラスがある場合、以下のように呼び出すことができます。
>>> iris.cls('User.ArrayTest').WriteContents(a)
myArray("color")="blue"
myArray("make")="Toyota"
myArray("model")="RAV4"
詳細は、"iris.arrayref()" を参照してください。
ObjectScript 側では、Python の builtins モジュールの dict() メソッドを使用して、Python ディクショナリを操作できます。
USER>set mycar = ##class(%SYS.Python).Builtins().dict()
USER>do mycar.setdefault("make", "Toyota")
USER>do mycar.setdefault("model", "RAV4")
USER>do mycar.setdefault("color", "blue")
USER>zwrite mycar
mycar=2@%SYS.Python ; {'make': 'Toyota', 'model': 'RAV4', 'color': 'blue'} ; <OREF>
USER>write mycar."__getitem__"("color")
blue
上記の例では、ディクショナリ・メソッド setdefault() を使用してキーの値を設定し、__getitem__() によってキーの値を取得しています。
リスト
Python では、リストは値のコレクションを格納しますが、キーを使用しません。リスト内の項目には、インデックスによってアクセスします。
>>> fruits = ["apple", "banana", "cherry"]
>>> print(fruits)
['apple', 'banana', 'cherry']
>>> print(fruits[0])
apple
ObjectScript では、Python の builtins モジュールの list() メソッドを使用して、Python リストを操作できます。
USER>set l = ##class(%SYS.Python).Builtins().list()
USER>do l.append("apple")
USER>do l.append("banana")
USER>do l.append("cherry")
USER>zwrite l
l=13@%SYS.Python ; ['apple', 'banana', 'cherry'] ; <OREF>
USER>write l."__getitem__"(0)
apple
上記の例では、リスト・メソッド append() を使用してリストに項目を追加し、__getitem__() によって指定したインデックスの値を取得しています(Python リストはゼロベースです)。
ObjectScript リストを Python リストに変換する場合は、%SYS.PythonOpens in a new tab の ToList() および ToListTyped() メソッドを使用できます。ObjectScript リストを指定すると、ToList() は同じデータを含む Python リストを返します。データを含む ObjectScript リストと、整数の ODBC データ型コードを含む 2 番目の ObjectScript リストを指定すると、ToListTyped() は各項目が 2 番目のリストで指定したデータ型を持つ、最初のリストと同じデータを含む Python リストを返します。
ODBC データ型のテーブルは、"データ型の整数コード" を参照してください。
一部の ODBC データ型は、同じ Python データ型に変換できます。
一部のデータ型は、Python パッケージ numpy をインストールする必要があります。
以下の例では、クラス User.Lists の Python メソッド Loop() がリスト内の項目を反復処理し、その値とデータ型を出力します。
ClassMethod Loop(pyList) [ Language = python ]
{
for x in pyList:
print(x, type(x))
}
その後、ToList() と ToListTyped() を以下のように使用できます。
USER>set clist = $listbuild(123, 456.789, "hello world")
USER>set plist = ##class(%SYS.Python).ToList(clist)
USER>do ##class(User.Lists).Loop(plist)
123 <class 'int'>
456.789 <class 'float'>
hello world <class 'str'>
USER>set clist = $listbuild(42, 42, 42, 42)
USER>set tlist = $listbuild(-7, 2, 3, 4)
USER>set plist = ##class(%SYS.Python).ToListTyped(clist, tlist)
USER>do ##class(User.Lists).Loop(plist)
True <class 'bool'>
42.0 <class 'float'>
42 <class 'decimal.Decimal'>
42 <class 'int'>
グローバル
ほとんどの場合、InterSystems IRIS に保存されているデータには、SQL を使用するか、永続クラスとそのプロパティおよびメソッドを使用してアクセスできます。しかし、グローバルと呼ばれる、基盤となるネイティブの永続データ構造に直接アクセスしたいこともあります。従来のデータにアクセスする場合、または SQL テーブルや永続クラスには適していないスキーマレスのデータを保存している場合は特にそうです。
単純化しすぎではありますが、グローバルは、キーと値のペアのディクショナリと考えることができます。(正確な説明は、"グローバルの概要" を参照してください。)
Python で記述された 2 つのクラス・メソッドを持つ、以下のクラスを考えてみます。
Class User.Globals
{
ClassMethod SetSquares(x) [ Language = python ]
{
import iris
square = iris.gref("^square")
for key in range(1, x):
value = key * key
square.set([key], value)
}
ClassMethod PrintSquares() [ Language = python ]
{
import iris
square = iris.gref("^square")
key = ""
while True:
key = square.order([key])
if key == None:
break
print("The square of " + str(key) + " is " + str(square.get([key])))
}
}
メソッド SetSquares() はキーの範囲をループして、グローバル ^square の各ノードで各キーの二乗を格納します。メソッド PrintSquares() はグローバルを走査し、キーに格納されている各キーと値を出力します。
Python シェルを起動して、クラスをインスタンス化し、コードを実行してどのように動作するかを確認してみましょう。
USER>do ##class(%SYS.Python).Shell()
Python 3.9.5 (default, May 31 2022, 12:35:47) [MSC v.1927 64 bit (AMD64)] on win32
Type quit() or Ctrl-D to exit this shell.
>>> g = iris.cls('User.Globals')
>>> g.SetSquares(6)
>>> g.PrintSquares()
The square of 1 is 1
The square of 2 is 4
The square of 3 is 9
The square of 4 is 16
The square of 5 is 25
今度は、組み込みの iris モジュールのいくつかのメソッドでグローバルにアクセスする方法を見てみましょう。
メソッド SetSquares() では、文 square = iris.gref("^square") はグローバル ^square への参照 (gref とも呼ばれます) を返します。
>>> square = iris.gref("^square")
文 square.set([key], value) は、キー key を持つ ^square のノードを値 value に設定します。例えば、^square のノード 12 を値 144 に設定できます。
>>> square.set([12], 144)
グローバルのノードを、次の短い構文で設定することもできます。
>>> square[13] = 169
メソッド PrintSquares() では、文 key = square.order([key]) は ObjectScript の $ORDER 関数のように、キーを入力として取り、グローバルの次のキーを返します。グローバルを走査するには、通常、キーがもう残っていないことを示す None が返されるまで order() を使用し続けます。キーが連続している必要はないため、キーの間にギャップがある場合でも、order() は次のキーを返します。
>>> key = 5
>>> key = square.order([key])
>>> print(key)
12
次に、square.get([key]) はキーを入力として取り、グローバルのそのキーの値を返します。
>>> print(square.get([key]))
144
再び、以下の短い構文を使用できます。
>>> print(square[13])
169
グローバルのノードがキーを持つ必要はありません。以下の文は、^square のルート・ノードに文字列を格納します。
>>> square[None] = 'Table of squares'
これらの Python コマンドが実際にグローバルに値を格納したことを示すために、Python シェルを終了し、ObjectScript で zwrite コマンドを使用して ^square の内容を出力します。
>>> quit()
USER>zwrite ^square
^square="Table of squares"
^square(1)=1
^square(2)=4
^square(3)=9
^square(4)=16
^square(5)=25
^square(12)=144
^square(13)=169
Python からグローバルにアクセスして操作する方法の詳細は、"グローバル参照 API" を参照してください。
ネームスペースの変更
InterSystems IRIS にはネームスペースという概念があり、各ネームスペースには、コードとデータを格納するための独自のデータベースがあります。これにより、あるネームスペースのコードおよびデータを、別のネームスペースのコードおよびデータと容易に分離できます。例えば、あるネームスペースに特定の名前のグローバルがある場合、別のネームスペースは同じ名前のグローバルを他のグローバルと競合する恐れなしに使用できます。
NSONE と NSTWO という 2 つのネームスペースがある場合、以下に示すように、ターミナルで ObjectScript を使用して、NSONE に ^myFavorite というグローバルを作成できます。その後、$namespace 特殊変数を設定して NSTWO に切り替え、このネームスペースに ^myFavorite という別個のグローバルを作成できます。(この例を複製するには、InterSystems IRIS インスタンス上にこれら 2 つのネームスペースを構成するか、既に存在する 2 つのネームスペースを使用できます。)
NSONE>set ^myFavorite("fruit") = "apple"
NSONE>set $namespace = "NSTWO"
NSTWO>set ^myFavorite("fruit") = "orange"
ここで、^myFavorite("fruit") は NSONE に "apple" の値、NSTWO に "orange" の値を持ちます。
組み込み Python を呼び出すと、これは現在のネームスペースを継承します。これをテストするには、Python から現在のネームスペースの名前を表示する %SYSTEM.SYSOpens in a new tab クラスの NameSpace() メソッドを呼び出し、^myFavorite("fruit") = "orange" であることを確認します。
NSTWO>do ##class(%SYS.Python).Shell()
Python 3.9.5 (default, Jun 2 2023, 14:12:21) [MSC v.1927 64 bit (AMD64)] on win32
Type quit() or Ctrl-D to exit this shell.
>>> iris.cls('%SYSTEM.SYS').NameSpace()
'NSTWO'
>>> myFav = iris.gref('^myFavorite')
>>> print(myFav['fruit'])
orange
$namespace を使用して ObjectScript でネームスペースを変更する方法を確認しました。組み込み Python では、iris.system.Process クラスの SetNamespace() メソッドを使用します。例えば、ネームスペース NSONE に切り替えて、^myFavorite("fruit") = "apple" であることを確認できます。
>>> iris.system.Process.SetNamespace('NSONE')
'NSONE'
>>> myFav = iris.gref('^myFavorite')
>>> print(myFav['fruit'])
apple
最後に、Python シェルを終了すると、ネームスペース NSONE にとどまります。
>>> quit()
NSONE>
組み込み Python からの ObjectScript ルーチンの実行
クラスやメソッドの代わりにルーチンを使用する古い ObjectScript コードが使用されていて、組み込み Python からルーチンを呼び出したい場合があります。このような場合、Python から iris.routine() メソッドを使用できます。
以下の例では、%SYS ネームスペースでの実行時にルーチン ^SECURITY を呼び出します。
>>> iris.routine('^SECURITY')
1) User setup
2) Role setup
3) Service setup
4) Resource setup
.
.
.
2 つの数値を加算する関数 Sum() を持つルーチン ^Math がある場合、以下の例では 4 と 3 を加算します。
>>> sum = iris.routine('Sum^Math',4,3)
>>> sum
7
例外処理
InterSystems IRIS 例外ハンドラは、Python の例外を処理し、それらをシームレスに ObjectScript に渡します。以下の例は、前述の Python ライブラリの例を基にして、存在しないファイルを使用して canvas.drawImage() を呼び出そうとするとどうなるかを示しています。ここで、ObjectScript は特殊変数 $zerror で例外を検出しています。
USER>try { do canvas.drawImage("C:\Sample\bad.png", 150, 600) } catch { write "Error: ", $zerror, ! }
Error: <THROW> *%Exception.PythonException <THROW> 230 ^^0^DO canvas.drawImage("W:\Sample\isc.png", 150, 600)
<class 'OSError'>: Cannot open resource "W:\Sample\isc.png" -
この場合、<class 'OSError'>: Cannot open resource "W:\Sample\isc.png" は Python から戻された例外です。
$Zerror の詳細は、"$ZERROR (ObjectScript)" を参照してください。
ObjectScript ステータス・エラーを Python 例外として生成する方法は、"check_status(status)" を参照してください。
Bytes 型と String 型
Python では、“bytes” データ型 (単に 8 ビット・バイトのシーケンス) のオブジェクトと、string (文字列を表す UTF-8 バイトのシーケンス) の間に明確な区別があります。Python では、bytes オブジェクトは決して変換されませんが、string はホスト・オペレーティング・システムによって使用される文字セットに応じて変換される場合があります (Latin-1 など)。
InterSystems IRIS では、bytes と string を区別しません。InterSystems IRIS では Unicode 文字列 (UCS-2/UTF-16) をサポートしていますが、256 より小さい値を含む文字列は、string と bytes のどちらにもなり得ます。このため、Python との間で string および bytes をやり取りする際は、以下のルールが適用されます。
-
InterSystems IRIS の string は文字列と見なされ、ObjectScript から Python に渡される際に UTF-8 に変換されます。
-
Python の string は、ObjectScript に戻されるときに、UTF-8 から InterSystems IRIS 文字列に変換され、その結果ワイド文字になります。
-
Python の bytes オブジェクトは、8 ビット文字列として ObjectScript に返されます。bytes オブジェクトの長さが最大文字列長を超えると、Python の bytes オブジェクトが返されます。
-
ObjectScript から Python に bytes オブジェクトを渡すには、##class(%SYS.Python).Bytes() メソッドを使用します。このメソッドは、基礎となる InterSystems IRIS 文字列を UTF-8 に変換しません。
以下の例は、InterSystems IRIS 文字列を bytes 型の Python オブジェクトに変換します。
USER>set b = ##class(%SYS.Python).Bytes("Hello Bytes!")
USER>zwrite b
b=8@%SYS.Python ; b'Hello Bytes!' ; <OREF>
USER>zwrite builtins.type(b)
4@%SYS.Python ; <class 'bytes'> ; <OREF>
InterSystems IRIS の最大文字列長である 3.8MB より大きい Python bytes オブジェクトを構築するには、bytearray オブジェクトを使用して、extend() メソッドにより bytes のより小さなチャンクを追加できます。最後に、bytearray オブジェクトを builtins の bytes() メソッドに渡して、bytes 表現を取得します。
USER>set ba = builtins.bytearray()
USER>do ba.extend(##class(%SYS.Python).Bytes("chunk 1"))
USER>do ba.extend(##class(%SYS.Python).Bytes("chunk 2"))
USER>zwrite builtins.bytes(ba)
"chunk 1chunk 2"
標準出力と標準エラーのマッピング
組み込み Python を使用する際、標準出力は InterSystems IRIS コンソールにマッピングされます。つまり、print() 文の出力はすべてターミナルに送信されます。標準エラーは、ディレクトリ <install-dir>/mgr にある InterSystems IRIS messages.log ファイルにマッピングされます。
例として、この Python メソッドについて考えてみましょう。
def divide(a, b):
try:
print(a/b)
except ZeroDivisionError:
print("Cannot divide by zero")
except TypeError:
import sys
print("Bad argument type", file=sys.stderr)
except:
print("Something else went wrong")
ターミナルでこのメソッドをテストする場合、以下のような画面が表示されます。
USER>set obj = ##class(%SYS.Python).Import("mymodule")
USER>do obj.divide(5, 0)
Cannot divide by zero
USER>do obj.divide(5, "hello")
0 による除算を試行すると、エラー・メッセージがターミナルに転送されますが、文字列による除算を試行すると、メッセージは messages.log に送信されます。
11/19/21-15:49:33:248 (28804) 0 [Python] Bad argument type
ファイルが乱雑になることを防ぐため、重要なメッセージのみが messages.log に送信されるようにする必要があります。