Python バインディングの使用法
この章では、Caché Python バインディングを使用する Python コードの具体例を示します。ここで説明する内容は以下のとおりです。
-
Python バインディングの基礎 — Caché データベース・オブジェクトへのアクセスと操作に関する基本事項です。
-
パラメータの参照渡し — Python バインディングにアクセスするための Python 固有の方法です。
-
コレクションの使用法 — Caché のリストと配列を反復処理します。
-
リレーションシップの使用法 — 埋め込みオブジェクトを操作します。
-
クエリの使用法 — Caché クエリとダイナミック SQL クエリを実行します。
-
%Binary データの使用法 — Caché %Binary と Python 整数リストの間でデータを移動します。
-
例外処理 — Python バインディングで発生した Python の例外とエラー・メッセージを処理します。
ここで紹介する例の多くは、サンプル・プログラムを変更したものです。コードを簡略化するため、引数処理とエラー・トラップ文 (try/catch) は除外されています。完全なサンプル・プログラムのロードと実行の詳細は、"サンプル・プログラム" を参照してください。
Python バインディングの基礎
Caché Python バインディング・アプリケーションは非常にシンプルです。以下は、完全なサンプル・プログラムです。
import codecs, sys
import intersys.pythonbind
# Connect to the Cache' database
url = "localhost[1972]:Samples"
user = "_SYSTEM"
password = "SYS"
conn = intersys.pythonbind.connection()
conn.connect_now(url,user,password, None)
database = intersys.pythonbind.database(conn)
# Create and use a Cache' object
person = database.create_new("Sample.Person", None)
person.set("Name","Doe, Joe A")
print "Name: " + str(person.get("Name"))
このコードは intersys.pythonbind モジュールをインポートして、以下の処理を実行します。
-
Caché データベースの Samples ネームスペースに接続します。
-
Caché データベースへの接続に必要な情報を定義します。
-
Connection オブジェクト (conn) を作成します。
-
Connection オブジェクトを使用して、Database オブジェクト (database) を作成します。
-
-
Caché オブジェクトを作成し、使用します。
-
Database オブジェクトを使用して、Caché Sample.PersonOpens in a new tab クラスのインスタンスを作成します。
-
Sample.PersonOpens in a new tab オブジェクトの Name プロパティを設定します。
-
Name プロパティを取得し、出力します。
-
以下のセクションでは、これらの基本処理について詳しく説明します。
Caché データベースへの接続
Caché データベースのネームスペースへ接続するための基本的な手順は以下のとおりです。
-
物理的な接続を確立します。
conn = intersys.pythonbind.connection() conn.connect_now(url,user,password, timeout)
connect_now() メソッドは、物理的に Caché データベースのネームスペースに接続します。url パラメータは、Connection オブジェクトがアクセスするサーバとネームスペースを指定します。Connection クラスには、Kerberos を使用した安全な接続を確立する secure_connect_now() メソッドもあります。これらのメソッドの詳細は、"接続" を参照してください。
-
論理接続を作成します。
database = intersys.pythonbind.database(conn)
Connection オブジェクトを使用して Database オブジェクトを作成します。これは、ネームスペースのクラスを使用してデータベースを操作できるようにする論理接続です。
以下のコードによって、Samples ネームスペースへの接続が確立されます。
address ="localhost" # server TCP/IP address ("localhost" is 127.0.0.1) port = "1972" # server TCP/IP port number namespace = "SAMPLES" # sample namespace installed with Cache' url = address + "[" + port + "]:" + namespace user = "_SYSTEM"; password = "SYS"; conn = intersys.pythonbind.connection() conn.connect_now(url,user,password, None) database = intersys.pythonbind.database(conn)
Caché Database メソッドの使用法
intersys.pythonbind.database クラスを使用すると、Caché クラスのメソッドを実行して、サーバ上の Caché オブジェクトに接続できます。Database クラスのメソッドで実行できる基本操作は以下のとおりです。
-
オブジェクトの作成。
create_new() メソッドを使用して、新しい Caché オブジェクトを作成します。構文は、以下のとおりです。
object = database.create_new(class_name, initial_value)
class_name は、database がアクセスするネームスペースの Caché クラスの名前です。例えば、Sample.PersonOpens in a new tab クラスの新規インスタンスを作成する場合は、以下の文を実行します。
person = database.create_new("Sample.Person", None)
この例では、person の初期値は定義されていません。
-
オブジェクトを開く。
openid() メソッドを使用して、既存の Caché オブジェクトを開きます。構文は、以下のとおりです。
object = database.openid(class_name, id, concurrency, timeout)
例えば、以下の文は、id の値が 1 である Sample.PersonOpens in a new tab オブジェクトを開きます。
person = database.openid("Sample.Person",str(1),-1,-1)
同時処理とタイムアウトは既定値に設定されています。
-
クラス・メソッドの実行。
以下の構文を使用してクラス・メソッドを実行できます。
result = database.run_class_method(classname,methodname,[LIST])
LIST は、メソッド引数のリストです。例えば、前述の例 database.openid() は以下のコードと同じです。
person = database.run_class_method("Sample.Person","%OpenId",[str(1)])
このメソッドは、Caché の ##class 構文と類似しています。以下はコードの例です。
list = database.run_class_method("%ListOfDataTypes","%New",[]) list.run_obj_method("Insert",["blue"])
上記のコードは、以下の ObjectScript コードとまったく同じです。
set list=##class(%ListOfDataTypes).%New() do list.Insert("blue")
Caché Object メソッドの使用法
intersys.pythonbind.object クラスは、Caché オブジェクトへのアクセスを提供します。Object クラスのメソッドで実行できる基本操作は以下のとおりです。
-
プロパティの取得と設定。
プロパティにアクセスするには、アクセサ・メソッド set() と get() を使用します。これらのメソッドの構文は以下のとおりです。
object.set(propname,value) value = object.get(propname)
以下に例を示します。
person.set("Name","Doe, Joe A") name = person.get("Name")
プライベート・プロパティと多次元プロパティは、Python バインディングを使用してアクセスすることができません。
-
オブジェクト・メソッドの実行。
オブジェクトのメソッドを実行するには run_obj_method() を呼び出します。
answer = object.run_obj_method(MethodName,[LIST]);
以下に例を示します。
answer = person.run_obj_method("Addition",[17,20])
このメソッドは、直接アクセスできない継承メソッド (%Save や %Id など) を呼び出すときに役立ちます。
-
オブジェクトの保存。
オブジェクトを保存するには、run_obj_method() を使用して %Save を呼び出します。
object.run_obj_method("%Save",[])
保存されているオブジェクトの ID を取得するには、run_obj_method() を使用して %Id を呼び出します。
id = object.run_obj_method("%Id",[])
パラメータの参照渡し
Python バインディングでは、引数の参照渡しが可能です。本来、Python には参照渡しのメカニズムが搭載されていないので、Python バインディングでは、呼び出された関数によって修正できるリストとしてすべての引数を渡します。例えば、以下のような Caché クラスがあるとします。
Class Caudron.PassByReference Extends %Persistent [ ProcedureBlock ] {
ClassMethod PassByReference(ByRef Arg1 As %String) {
set Arg1="goodbye"
}
}
以下のコードは引数を参照として渡します。
list = ["hello"]
print "passed to method PassByReference = " + str(list[0])
database.run_class_method("Caudron.PassByReference","PassByReference",list)
print "returned by reference = " + str(list[0])
print 文によって次のように出力されます。
passed to method PassByReference = hello
returned by reference = goodbye
コレクションとリストの使用法
Caché %Collection オブジェクトは、その他の Python バインディング・オブジェクトと同じように処理されます。Caché %ListOpens in a new tab 変数は、Python の配列参照にマップされます。この後のセクションでは、コレクションとリスト両方の使用方法を示します。
%Collection オブジェクト
コレクションを操作するには、Caché %Collection クラスのオブジェクト・メソッドを使用します。以下の例は、Caché %ListOfDataType コレクションを操作する方法を示しています。
# Create a %ListOfDataTypes object and add a list of colors
newcolors = database.create_new("%ListOfDataTypes", None)
color_list = ['red', 'blue', 'green']
print "Adding colors to list object:"
for i in range(len(color_list)):
newcolors.run_obj_method("Insert",[color_list[i]])
print " added >"+ str(color_list[i]) +"<"
# Add the list to a Sample.Person object.
person = database.openid("Sample.Person",str(1),-1,0)
person.set("FavoriteColors",newcolors)
# Get the list back from person and print it out.
colors = person.get("FavoriteColors")
print "\nNumber of colors in 'FavoriteColors' list: %d"\
% (colors.get("Size"))
index = [0]
while (index[0] != None):
color = colors.run_obj_method("GetNext",index)
if (index[0] != None):
print " Color #%d = >%s<" % (index[0], str(color))
# Remove and replace the second element
index = 2
if (colors.get("Size") > 0):
colors.run_obj_method("RemoveAt",[index])
colors.run_obj_method("InsertAt",['purple',index])
newcolor = colors.run_obj_method("GetAt",[index])
print "\nChanged color #%d to %s." % (index, str(newcolor))
%List 変数
Python バインディングでは、Caché %ListOpens in a new tab 変数が Python 配列参照にマップされます。
Python 配列にはサイズ制限がありませんが、Caché %ListOpens in a new tab 変数は約 32 KB に制限されています。実際の上限は、データ型と、各要素に必要なヘッダ・データのサイズによって異なります。%ListOpens in a new tab データのサイズがこの上限に達する可能性がある場合は、適切なエラー・チェック (この後の例を参照) を使用してください。
このセクションの例では、以下の Caché クラスが使用されています。
Class Sample.List Extends %Persistent
{
Property CurrentList As %List;
Method InitList() As %List {
q $ListBuild(1,"hello",3.14) }
Method TestList(NewList As %List) As %Integer {
set $ZTRAP="ErrTestList"
set ItemCount = $ListLength(NewList)
if (ItemCount = 0) {set ItemCount = -1}
q ItemCount
ErrTestList
set $ZERROR = ""
set $ZTRAP = ""
q 0 }
}
TestList() メソッドを使用して、Python 配列が有効な Caché リストかどうかをテストします。リストが長すぎる場合はエラー・トラップが実行され、0 (Python false) が返されます。リストが有効な場合は、要素の数が返されます。有効なリストの要素数が 0 の場合は、-1 が返されます。
以下のコードは Sample.List オブジェクトを生成し、事前定義された Caché リストを InitList() メソッドから取得します。さらに、そのリストを Python 配列に変換し、その配列に関する情報を表示します。
listobj = database.create_new("Sample.List",None)
mylist = listobj.run_obj_method("InitList",[])
print "Initial List from Cache:"
print "array contents = " + str(mylist)
print "There are %d elements in the list:" % (len(mylist))
for i in range(len(mylist)):
print " element %d = [%s]" % (i,mylist[i])
このコードは以下の出力を生成します。
Initial List from Cache:
array contents = [1, 'hello', 3.1400000000000001]
There are 3 elements in the list:
element 0 = [1]
element 1 = [hello]
element 2 = [3.14]
element 3 で、Caché の null リスト要素は、Python 配列の値 None に対応します。
以下のコードは両方向にリストを渡します。簡単な Python 配列を作成し、Caché オブジェクトの CurrentList プロパティに格納します。次に、同じプロパティからリストを取得し、それを Python 配列に再変換します。
# Generate a small Python list, pass it to Cache and get it back.
oldarray = [1, None, 2.78,"Just a small list."]
listobj.set("CurrentList",oldarray)
newarray = listobj.get("CurrentList")
print "\nThe 'CurrentList' property now has %d elements:"\
% (len(newarray))
for i in range(len(newarray)):
print " element %d = [%s]" % (i,newarray[i])
このコードは以下の出力を生成します。
The 'CurrentList' property now has 4 elements:
element 0 = [1]
element 1 = [None]
element 2 = [2.78]
element 3 = [Just a small list.]
Cache %ListOpens in a new tab 変数に Python 配列全体を格納できるようにすることが重要です。以下のコードは、サイズが過度に大きい Python 配列を作成し、CurrentList プロパティへの格納を試行します。
# Create a large array and print array information.
longitem = "1022 character element" + ("1234567890" * 100)
array =["This array is too long."]
cache_list_size = len(array[0])
while (cache_list_size < 32768):
array.append(longitem)
cache_list_size = cache_list_size + len(longitem)
print "\n\nNow for a HUGE list:"
print "Total bytes required by Cache' list: more than %d" % (cache_list_size)
print "There are %d elements in the ingoing list.\n" % (len(array))
# Check to see if the array will fit.
bool = listobj.run_obj_method("TestList",[array])
print "TestList reports that "
if (bool):
print "the array is OK, and has %d elements.\n" % bool
else:
print "the array will be damaged by the conversion.\n"
# Pass the array to Cache', get it back, and display the results
listobj.set("CurrentList",array)
badarray = listobj.get("CurrentList")
print "There are %d elements in the returned array:\n" % (len(badarray))
for i in range(len(badarray)):
line = str(badarray[i])
if (len(line)> 80):
# long elements are shortened for readability.
line = line[:][:9] + "..." + line[:][-50:]
print " element %d = [%s]\n" % (i, line)
出力をわかりやすく示すため、正常なセクションについては要素が省略されています。Unicode システムでは以下の出力が生成されます。
Now for a HUGE list:
Total bytes in array: at least 33884
There are 34 elements in the ingoing array.
~
TestList reports that the array will be damaged by the conversion.
There are 17 elements in the returned array :
element 1 = [This list is too long.]
element 2 = [1022 chara...90123456789012345678901234567890]
element 3 = [1022 chara...90123456789012345678901234567890]
element 4 = [1022 chara...90123456789012345678901234567890]
element 5 = [1022 chara...90123456789012345678901234567890]
element 6 = [1022 chara...90123456789012345678901234567890]
element 7 = [1022 chara...90123456789012345678901234567890]
element 8 = [1022 chara...90123456789012345678901234567890]
element 9 = [1022 chara...90123456789012345678901234567890]
element 10 = [1022 chara...90123456789012345678901234567890]
element 11 = [1022 chara...90123456789012345678901234567890]
element 12 = [1022 chara...90123456789012345678901234567890]
element 13 = [1022 chara...90123456789012345678901234567890]
element 14 = [1022 chara...90123456789012345678901234567890]
element 15 = [1022 chara...90123456789012345678901234567890]
element 16 = [1022 chara...90123456789012345678901234567890]
element 17 = [1022 chara...901234567st is too long.ï´√ȇ1022 c]
破損したリストに記録されているのは、元の 34 要素のうちの 17 要素のみなので、17 要素が破損していることになります。
リレーションシップの使用法
リレーションシップは、リレーションシップ・オブジェクトとそのメソッドによってサポートされています。
以下の例では、company オブジェクトと employee オブジェクト・セット間の一対多リレーションシップを使用してレポートを生成します。コードでリレーションシップ・オブジェクト emp_relationship を使用すれば、company オブジェクトに関連付けられているすべての employee オブジェクトにアクセスできます。
company = database.openid("Sample.Company",str(1),-1,0)
emp_relationship = company.get("Employees")
index = [None]
print "Employee list:\n"
while 1:
employee = emp_relationship.run_obj_method("GetNext",index)
# "GetNext" sets index[0] to the next valid index, or
# to None if there are no more records.
if (employee != None):
i = str(index[0])
name = str(employee.get("Name"))
title = str(employee.get("Title"))
company = employee.get("Company")
compname = str(company.get("Name"))
SSN = str(employee.get("SSN"))
print " employee #%s:" % i
print " name=%s SSN=%s" % (name, SSN)
print " title=%s companyname=%s\n"% (title, compname)
else:
break
以下のコードは、新しい従業員レコードを作成してリレーションシップに追加し、company を保存したときに employee の情報も自動的に保存します。
new_employee = database.create_new("Sample.Employee","")
new_employee.set("Name",name)
new_employee.set("Title",title)
new_employee.set("SSN",SSN)
emp_relationship.run_obj_method("Insert", new_employee)
company.run_obj_method("%Save")
クエリの使用法
クエリを実行するための基本的な手順は以下のとおりです。
-
クエリ・オブジェクトを作成します。
query = intersys.pythonbind.query(database)
ここで、database は intersys.pythonbind.database オブジェクト、query は intersys.pythonbind.query オブジェクトです。
-
クエリを作成します。
SQL クエリは prepare() メソッドを使用します。
sql_code = query.prepare(sql_query)
sql_query は、実行するクエリを含む文字列です。
Caché クラスのクエリは prepare_class() メソッドを使用します。
sql_code = query.prepare_class(class_name, query_name)
-
パラメータ値を割り当てます。
query.set_par(idx, val)
set_par() メソッドは、idx パラメータに値 val を割り当てます。既存のパラメータの値を変更するには、新しい値を指定して set_par() を再度呼び出します。Query クラスには、既存のパラメータに関する情報を返すいくつかのメソッドが用意されています。
-
クエリを実行します。
sql_code = query.execute()
execute() メソッドは、set_par() の呼び出しで定義されているパラメータを使用して、結果セットを生成します。
-
データ行を取得します。
data_row = query.fetch([None])
fetch() メソッドを呼び出すたびに、結果セットからデータ行が取得され、そのデータがリストとして返されます。取得するデータがない場合は、空のリストが返されます。Query クラスには、行内の列に関する情報を返すいくつかのメソッドが用意されています。
Sample.Person からデータを取得する簡単な SQL クエリを以下に示します。
# create a query sqlstring ="SELECT ID, Name, DOB, SSN \ FROM SAMPLE.PERSON \ WHERE Name %STARTSWITH ?" query = intersys.pythonbind.query(database) query.prepare(sqlstring) query.set_par(1,"A") query.execute(); # Fetch each row in the result set, and print the # name and value of each column in a row: while 1: cols = query.fetch([None]) if len(cols) == 0: break print "\nRow %s ===================" % str(cols[0]) print " %s: %s, %s: %s, %s: %s" \ % (str(query.col_name(2)), str(cols[1]), \ str(query.col_name(3)), str(cols[2]), \ str(query.col_name(4)), str(cols[3]))
intersys.pythonbind.Query クラスの詳細は、"Python クライアント・クラス・リファレンス" の章の "クエリ" を参照してください。Caché のクエリの詳細は、"Caché オブジェクトの使用法" の "クラス・クエリの定義と使用" を参照してください。
%Binary データの使用法
Python バインディングでは、Python の pack() 関数と unpack() 関数を使用して、Caché %BinaryOpens in a new tab と Python 整数リスト間でデータを変換します。Caché バイナリ・データの個々のバイトは、Python では 0 ~ 255 の整数として表されます。
この例で、バイナリには最初は "hello" が格納されています。以下のコードでこのバイナリを "hellos" に変更します
binary = reg.get("MyByte")
for c in binary:
print "%c" % c
print type(ord("s"))
binary.append(ord("s"))
reg.set("MyByte",binary)
binary = reg.get("MyByte")
for c in binary:
print "%c" % c
例外処理
Python バインディングは Python の例外を使用して、C バインディングおよびその他で発生したエラーを返します。Python の例外を使用する例を次に示します。
try:
#some code
except intersys.pythonbind.cache_exception, err:
print "InterSystems Cache' exception"
print sys.exc_type
print sys.exc_value
print sys.exc_traceback
print str(err)
エラー・レポート
引数または返り値を処理する際、C バインディングからのエラー・メッセージは Python バインディング層によって特別にフォーマットされます。この情報は、インターシステムズのサポート窓口が問題を診断する際に使用します。
以下はエラー・メッセージの例です。
file=PythonBIND.xs line=71 err=-1 message=cbind_variant_set_buf()
cpp_type=4 var.cpp_type=-1 var.obj.oref=1784835886
class_name=%Library.RelationshipObject mtd_name=GetNext argnum=0
エラー・メッセージの構成要素は以下のとおりです。
メッセージ |
意味 |
---|---|
file=PythonBIND.xs |
エラーが発生したファイル。 |
line=71 |
ファイル内の行番号。 |
err=-1 |
C バインディングから返されるコード。 |
message= cbind_variant_set_buf() |
C バインディングのエラー・メッセージ。 |
cpp_type=4 |
メソッド引数の cpp タイプまたは返り値のタイプ。 |
var.cpp_type=-1 |
variant の cpp タイプ。 |
var.obj.oref=1784835886 |
variant の oref。 |
class_name= %Library.RelationshipObject |
メソッド呼び出しの対象となるオブジェクトのクラス名。 |
mtd_name=GetNext |
メソッド名。 |
argnum=0 |
引数の番号。0 は最初の引数、-1 は返り値を表します。 |