Python3 から Python2.7 で作成したコマンド (func_caller.py) を実行することで Python2.7 のモジュールで定義された関数を呼べるようにしてみました。
なんでこんな面倒くさいことやるかっていうと、現在の Ubuntu では libvirt モジュールが Python2.7 用のものはありますが Python3 用のものがなく、だけど Python3 から libvirt モジュールを使いたいからです。
「 Python3 から XML-RPC 経由で Python2.7 の libvirt を使って KVM 操作 」では XML-RPC を使いましたが、常時 XML-RPC サーバーを起動しておくのも大変なので func_caller.py を使う方法に変更する予定です。 (この件はまた後日投稿します。)
Python2.7 の関数を呼ぶための func_caller.py 設置
適当なディレクトリに以下の func_caller.py を設置します。
$ cat func_caller.py
#!/usr/bin/env python2.7
# coding: utf-8
'''
func_caller.py
~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2014 by Fishrimper.
:license: BSD, see LICENSE for details.
'''
import argparse
import importlib
import inspect
import types
import json
from pprint import pprint
def callAndGetResult(ClassOrFunc, sArgsInJson):
if sArgsInJson == None:
return ClassOrFunc()
else:
oArgs = json.loads(sArgsInJson)
if isinstance(oArgs, list):
return ClassOrFunc(*oArgs)
elif isinstance(oArgs, dict):
return ClassOrFunc(**oArgs)
else:
raise Exception(oArgs)
def main():
oParser = argparse.ArgumentParser()
oParser.add_argument('module_name')
oParser.add_argument('--class_name')
oParser.add_argument('--class_init_args', help='class init args in json format.')
oParser.add_argument('func_name')
oParser.add_argument('--func_args', help='function args in json format.')
oArgs = oParser.parse_args()
mModule = importlib.import_module(oArgs.module_name)
if oArgs.class_name == None:
oFunc = getattr(mModule, oArgs.func_name)
else:
cClass = getattr(mModule, oArgs.class_name)
assert inspect.isclass(cClass), oArgs
oInstance = callAndGetResult(cClass, oArgs.class_init_args)
oFunc = getattr(oInstance, oArgs.func_name)
ltypeFuncs = [
types.TypeType,
types.BuiltinFunctionType,
types.FunctionType,
types.MethodType,
]
for typeFunc in ltypeFuncs:
if isinstance(oFunc, typeFunc):
break
else:
raise Exception(oArgs, type(oFunc))
oResult = callAndGetResult(oFunc, oArgs.func_args)
print(json.dumps(oResult))
if __name__ == '__main__':
main()
実行権限を付けます
$ chmod +x func_caller.py
func_caller を使ってコマンドラインから Python2.7 のモジュールで定義された関数を呼ぶことができます。
- 例えば、
$ ./func_caller.py sysconfig get_python_version "2.7"
とすると、 Python2.7 の sysconfig モジュール の get_python_version() 関数を呼ぶことができます。
関数の実行結果として返された “2.7” という文字列が json フォーマットに変換されて標準出力に表示されます。
- func_caller.py の中では以下と同じようなことをしています。
$ python2.7 Python 2.7.3 (default, Feb 27 2014, 19:58:35) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> >>> import json >>> import sysconfig >>> >>> ret = sysconfig.get_python_version() >>> ret_in_json = json.dumps(ret) >>> >>> print(ret_in_json) "2.7"
- 関数に引数を渡すこともできます。
$ ./func_caller.py calendar weekday --func_args '[2014, 5, 22]' 3
引数も JSON フォーマットで渡します。
これは以下の Python2.7 コードを実行したのと同義です。
>>> import json, calendar >>> print(json.dumps(calendar.weekday(2014, 5, 22))) 3
- 関数にキーワード引数を渡すには、JSON のオブジェクト (Python でいうことろの辞書) で指定します。
$ ./func_caller.py calendar weekday --func_args '{"day": 22, "month": 5, "year": 2014}' 3
以下の Python2.7 コードと同義です。
>>> import json, calendar >>> print(json.dumps(calendar.weekday(day=22, month=5, year=2014))) 3
- モジュールで定義されたクラスでインスタンスを作成し、インスタンスのメソッドを呼ぶこともできます。
$ ./func_caller.py datetime --class_name date --class_init_args '[2014, 5, 22]' strftime --func_args '["%Y/%m/%d"]' "2014/05/22"
datetime モジュールの date クラスでインスタンスを作成し、strftime() メソッドを呼んでいます。
以下の Python2.7 コードと同義です。
>>> import json, datetime >>> print(json.dumps(datetime.date(2014, 5, 22).strftime("%Y/%m/%d"))) "2014/05/22"
- モジュール名を __builtin__ にすると ビルトイン関数 を呼ぶことができます。
$ ./func_caller.py __builtin__ pow --func_args '[10, 2]' 100
以下の Python2.7 コードと同義です。
>>> import json >>> json.dumps(pow(10, 2)) '100'
Python3 から func_caller.py を呼ぶ
Python3 で mPy27_func_caller モジュールをロードし、このモジュールから func_caller.py を呼ぶことにより Python2.7 の関数を使います。
適当なディレクトリに以下の mPy27_func_caller.py を設置します。
$ cat mPy27_func_caller.py
#!/usr/bin/env python3
# coding: utf-8
'''
mPy27_func_caller.py
~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2014 by Fishrimper.
:license: BSD, see LICENSE for details.
'''
import json
import subprocess
import os
class Py27_func_caller:
def __init__(self, sPathFuncCaller):
assert os.access(sPathFuncCaller, os.X_OK), ('Not executable.', sPathFuncCaller)
self.__sPathFuncCaller = sPathFuncCaller
def call(self, sModuleName, sFuncName, xFuncArgs=None, sClassName=None, xClassInitArgs=None):
lArgs = [sModuleName, sFuncName]
if xFuncArgs != None:
lArgs += ['--func_args', json.dumps(xFuncArgs)]
if sClassName != None:
lArgs += ['--class_name', sClassName]
if xClassInitArgs != None:
lArgs += ['--class_init_args', json.dumps(xClassInitArgs)]
sRetJson = subprocess.check_output([self.__sPathFuncCaller] + lArgs).decode()
return json.loads(sRetJson)
- 以下、簡単な使い方の例です。
$ python3 Python 3.2.3 (default, Feb 27 2014, 21:31:18) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> >>> from mPy27_func_caller import Py27_func_caller >>> oPy27 = Py27_func_caller('./func_caller.py') # 引数で func_caller.py のパスを指定します。 >>> oPy27.call(sModuleName='sysconfig', sFuncName='get_python_version') '2.7'
Python3 から Python2.7 の sysconfig モジュールの get_python_version() を呼んでいるので Python2.7 のバージョン ‘2.7’ が返って来ました。
- モジュールのクラス名や引数の指定は以下のようにします。
>>> oPy27.call(sModuleName='datetime', sClassName='date', xClassInitArgs=[2014, 5, 22], sFuncName='strftime', xFuncArgs={'format': "%Y/%m/%d"}) '2014/05/22'
Python2.7 側では以下のように呼ばれてます。
import datetime datetime.date(2014, 5, 22).strftime(format="%Y/%m/%d")
なお、引数には JSON フォーマットで表現できないものは指定できません。
また、関数の戻り値も JSON フォーマットで表現できないものはエラーになります。
0 件のコメント:
コメントを投稿