記事
· 2024年7月5日 2m read

%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プログラムを更に有効活用出来るようになります。

ディスカッション (5)4
続けるにはログインするか新規登録を行ってください

はじめまして、こんな方法は如何ですか?

出来るだけ、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>