2014年5月29日木曜日

Python3 から Python2.7 モジュールの関数をコマンド (func_caller.py) 経由で呼ぶ

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 件のコメント:

コメントを投稿