%DynamicObjectを外部Pythonの引数に利用する方法
IRISでPythonを扱う時に、既存の%DynamicObject型の値をそのまま利用したいと思うのですが、Embedded Pythonは自動で%DynamicObjectをdict型にはしてくれません。親和性はとてもあるのですが。。。
そこで、既存プログラムで生成した%DynamicObject型の値をPython側、特に外部のPythonファイル側でdict型を期待している関数に利用するにはどうすれば良いか。
少しスマートではありませんが、%DynamicObjectを一旦JSON文字列に置き換え、Embedded Python 内でJSON文字列からdict型に変換する方法しかないようです。
以下が、その手順です。
Set data = {}
Set data.name = "hanako"
Set data.age = 20
Do ..testPython(data)
ClassMethod testPython(arg As %DynamicObject) [ Language = python ]
{
import json
import pythonfile
data = json.loads(arg._ToJSON())
pythonfile.test(data)
}
pythonfile.py
def test(arg):
name = arg.name
age = arg.age
arg._ToJSON()で%DynamicObject型の値をJSON文字列に変換し、更に、json.loads()でJSON文字列をdict型に置き換えています。
"_ToJSON"は、%DynamicObject型の%ToJSONメソッドです。
Pythonファイルの関数からdict型で戻り値がある場合には、この逆を行えば%DynamicObjectで戻すことが出来ます。
Set data = {}
Set data.name = "hanako"
Set data.age = 20
Set sc = ..testPython(data)
Write sc.status,!
ClassMethod testPython(arg As %DynamicObject) As %DynamicObject [ Language = python ]
{
import json
import pythonfile
result = {}
data = json.loads(arg._ToJSON())
re = pythonfile.test(data)
result.fromJSON(json.dumps(re))
return result
}
pythonfile.py
def test(arg):
result = {
"age":arg.age,
"status":"ok"
}
return result
これで、既存のCOSプログラムと、既存のPythonプログラムを更に有効活用出来るようになります。
Comments
はじめまして、こんな方法は如何ですか?
出来るだけ、ObjectScript、Python、それぞれで、自身のデータ型で処理できるように、渡す前後で、相手のタイプに変換/逆変換出来れば、透過性がよくなるかもしれません。
Class Py.Demo Extends%RegisteredObject
{
/// d ##Class(Py.Demo).Test()ClassMethod Test()
{
set builtins = ##class(%SYS.Python).Builtins()
set json = ##class(%SYS.Python).Builtins().Import("json")
;# ObjectScritタイプset oslist=[1.23456789012345678901234,true,false,null,0,1,"","英語"]
set osdict={"A":33,"a":"lower case","漢字":"日本"}
set osdict.list=oslist
;# Pythonタイプに変換set pydict = builtins.dict() ;この行余計かもset pydict = json.loads(osdict.%ToJSON())
set pylist = builtins.list() ;この行余計かもset pylist = json.loads(oslist.%ToJSON())
zw builtins.type(pydict)
zw builtins.type(pylist)
;# Pythonタイプで引数参照で渡す;# *******************set ret = ##Class(Py.Demo).PythonProc( pydict , pylist )
;# *******************;# 参照引数渡しなので、結果として、Python側で変更された値が、引数に反映される;# ObjectScript型に戻すset osdict = {}.%FromJSON(..jsondumps(pydict))
set oslist = [].%FromJSON(..jsondumps(pylist))
#; 以下でも jsondumps 使わず、文字化けは解消します
#;set osdict= {}.%FromJSON($zcvt($zcvt(json.dumps(pydict),"I","JS"),"I","JSON"))
#;set oslist= [].%FromJSON($zcvt($zcvt(json.dumps(pylist),"I","JS"),"I","JSON"))w !,osdict.UPDATE
w !,oslist."0"w !
#; 返値も、構造が分かれば、Pythonの組み込み関数を利用して分解し、ObjectScript型に変換
#; return タプル( dict , list ) を前提set retdict = {}.%FromJSON(..jsondumps(ret."__getitem__"(0)))
set retlist = [].%FromJSON(..jsondumps(ret."__getitem__"(1)))
w !
zw retdict
w !
zw retlist
}
ClassMethod jsondumps(str) As%SYS.Python [ Language = python ]
{
#; 文字化け対応のキーワード指定(ensure_ascii=False)
#; ObjectScritで、キーワード引数指定の仕方が分からないので python クラスにする
import json
return json.dumps(str,ensure_ascii=False)
}
ClassMethod PythonProc(dict As%SYS.Python, list As%SYS.Python) As%SYS.Python [ Language = python ]
{
#; Python側では、自身のデータタイプでアクセス
import json
print(type(dict))
print(dict)
print(type(list))
print(list)
dict['UPDATE']='dict Python側で変更しました'
list[0]='list ここも変更しました'
return ( dict , list )
}
}
処理結果
USER>d##Class(Py.Demo).Test()
6@%SYS.Python; <class 'dict'> ; <OREF>6@%SYS.Python; <class 'list'> ; <OREF>
<class 'dict'>
{'A': 33, 'a': 'lower case', '漢字': '日本', 'list': [1.2345678901234567, True,False, None, 0, 1, '', '英語']}
<class 'list'>
[1.2345678901234567, True, False, None, 0, 1, '', '英語']
dict Python側で変更しました
list ここも変更しました
retdict={"A":33,"a":"lower case","漢字":"日本","list":[1.2345678901234567,true,false,null,0,1,"","英語"],"UPDATE":"dict Python側で変更しました"} ; <DYNAMIC OBJECT>
retlist=["list ここも変更しました",true,false,null,0,1,"","英語"] ; <DYNAMIC ARRAY>
USER>imaさん、返信ありがとうございます。
COS側で、.Builtins().dict() でdict型変数を作った上で、Pythonへ渡す方法も有りだと思います。
今回、その方法を取らなかった理由は、Embedded Python の仕組みが出てくる前から既にIRISとPythonを連携していたので、既存ソースから極力少ない変更で既存のpythonファイルを活用しながらも連携方法をEmbedded Pythonに移行する方法としたからです。説明が足らずに申し訳ございません。
また、引数の型や、戻り値の型が何でも%SYS.Pythonになってしまうのが、自分的にとっては分かりにくいと思ったので、そこはあえてCOS側の型にしていました。
以下は、PythonからObjectScriptを呼び出す場合のJSONデータの渡し方のサンプルです。
Class Py.Demo2 Extends%RegisteredObject
{
/// d ##Class(Py.Demo2).Test()/// PythonからObJectScriptを呼び出す場合のJSONデータの渡し方案/// ObJectScriptを呼び出す前後で、Python型からObJectScript型に変換/逆変換するClassMethod Test() [ Language = python ]
{
import iris
import json
list = [1.23456789012345678901234,True,False,None,0,1,'']
dict = {'A':33,'a':'lower case','漢字':'日本'}
dict['list'] = list
#; ObjectScript型へ変換
osdict = iris.cls("%DynamicObject")._New()._FromJSON(json.dumps( dict , ensure_ascii=False ))
oslist = iris.cls("%DynamicArray")._New()._FromJSON(json.dumps( list , ensure_ascii=False ))
print(f'type(osdict) = {type(osdict)}')
print(f'osdict = {osdict._ToJSON()}')
print(f'type(oslist) = {type(oslist)}')
print(f'oslist = {oslist._ToJSON()}')
arg = iris.ref("引数参照渡し")
print(f'type(arg)={type(arg)}')
arf = iris.arrayref(dict) #; アレイ参照渡し dictのみでlistはエラーprint(f'type(arf)={type(arf)}')
print('ObjectScript Proc 呼出し')
#;****************************
ret = iris.cls("Py.Demo2").OSProc( osdict , oslist , arg , arf )
#;****************************print('ObjectScript Proc 終了')
print(f'返値型 = {type(ret)}')
print(f'返値 = {ret}')
#; Python型へ変換
dict = json.loads(osdict._ToJSON())
list = json.loads(oslist._ToJSON())
print(type(dict))
print(dict)
print(type(list))
print(list)
print(f'arg.value={arg.value}')
iris.execute('zw ^||TEST') ;#ネストはなし(dict['list'])
#: 以下の iris.gref は、^||...はエラーになるのでNOP
#; g = iris.gref('^||TEST')
#; for key in g.keys([]):
#; value = g[key]
#; print(f'{key} = {value}')
}
ClassMethod OSProc(ByRef Obj As%DynamicObject, ByRef Arr As%DynamicObject, ByRef Arg, ByRef Arf) As%DynamicAbstractObject
{
s Obj.UPDATE="dict Python側で変更しました"s Arr."0"="list ここも変更しました"s Arg=Arg_" 追加しました"w !,"Obj=",Obj.%ToJSON()
w !,"Arr=",Arr.%ToJSON()
w ! zw Arf
k ^||TEST
m ^||TEST=Arf
w !,"プロセス・プライベート・グローバル ^||TEST にセットしました",!
q"Ok"
#; dict で返す場合
#; s json = ##class(%SYS.Python).Builtins().Import("json")
#; s dict = json.loads(Obj.%ToJSON())
#; q dict
}
}
実行結果
USER>d##Class(Py.Demo2).Test()
type(osdict) = <class 'iris.%Library.DynamicObject'>
osdict = {"A":33,"a":"lower case","漢字":"日本","list":[1.2345678901234567,true,false,null,0,1,""]}
type(oslist) = <class 'iris.%Library.DynamicArray'>
oslist = [1.2345678901234567,true,false,null,0,1,""]
type(arg)=<class 'iris.ref'>
type(arf)=<class 'iris.arrayref'>
ObjectScript Proc 呼出し
Obj={"A":33,"a":"lower case","漢字":"日本","list":[1.2345678901234567,true,false,null,0,1,""],"UPDATE":"dict Python側で変更しました"}
Arr=["list ここも変更しました",true,false,null,0,1,""]
Arf("A")=33
Arf("a")="lower case"
Arf("list")=4@%SYS.Python; [1.2345678901234567, True, False, None, 0, 1, ''] ; <OREF>
Arf("漢字")="日本"
プロセス・プライベート・グローバル ^||TEST にセットしました
ObjectScript Proc 終了
返値型 = <class 'str'>
返値 = Ok
<class 'dict'>
{'A': 33, 'a': 'lower case', '漢字': '日本', 'list': [1.2345678901234567, True, False, None, 0, 1, ''], 'UPDATE': 'dict Python側で変更しました'}
<class 'list'>
['list ここも変更しました', True, False, None, 0, 1, '']
arg.value=引数参照渡し 追加しました
^||TEST("A")=33
^||TEST("a")="lower case"
^||TEST("list")="4@%SYS.Python"
^||TEST("漢字")="日本"
USER>2024.2からこのあたりいろいろと機能強化が行われる様です。
英語ドキュメントですが、以下ご参考まで
https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cl…
訂正します。
現バージョン(2024.1)にもこれらの機能はありました。