只管打code

プログラミングなど

2009年10月18日日曜日

python ctypes

Python の ctypes モジュールについて調べてみた。

下記のような dll を作成して、ctypes モジュールで引数、戻り値の設定の仕方をどのようにしたらよいか調査した。
---- ctypes_test.dll ----

extern "C" {
__declspec(dllexport) int plus(int, int);
__declspec(dllexport) void plus_pointer(int*, int*, int*);
__declspec(dllexport) int* alloc_int();
__declspec(dllexport) void free_int(int*);
__declspec(dllexport) int set_calc_func(int (*)(int, int));
};
int plus(int lhs, int rhs) {
 return lhs + rhs;
}
void plus_pointer(int* pLhs, int* pRhs, int* pResult) {
 *pResult = *pLhs + *pRhs;
}
int* alloc_int() {
 int* p = new int(10);
 std::cout << "new :" << p << std::endl;;
 return p;
}
void free_int(int* p) {
 std::cout << "delete :" << p << std::endl;;
 delete p;
}
int set_calc_func(int (*func)(int, int)) {
 return func(100, 25);
}


---- python側 ----

# -*- coding: utf-8 -*-

from ctypes import *
test = cdll.LoadLibrary('ctypes_test.dll')

# ---- 単純な足し算1 ----
result = test.plus(10, 20)
print result # ok 30
# 整数と文字列は気にせず利用することができるよう工夫されているそうな

# ---- 単純な足し算2 ----
lhs = c_int(32)
rhs = c_int(41)
result = test.plus(lhs, rhs)
print result # ok 73

# ---- アドレス渡し ----
lhs = c_int(15)
rhs = c_int(25)
result = c_int(0)
test.plus_pointer(byref(lhs), byref(rhs), byref(result))
print result # ok c_long(40)
print result.value # ok 40

# ---- ポインタ渡し ----
lhs = c_int(21)
rhs = c_int(47)
result = c_int(0)
test.plus_pointer(pointer(lhs), pointer(rhs), pointer(result))
print result # ok c_long(68)
print result.value # ok 68

# ---- ポインタ取得 ----

# "デフォルトでは、関数は C int を返すと仮定されます。
# 他の戻り値の型を指定するには、関数オブジェクトの restype 属性に設定します。"
# とのこと。
alloc_func = test.alloc_int
alloc_func.restype = POINTER(c_int) # 戻り値が int* 型であることを指定
ptr = alloc_func() # ok new
print ptr.contents # ok c_long 10
print ptr.contents.value # ok 10
test.free_int(ptr) # ok delete (new の場合と同じアドレスをdeleteしているのでok)

# ---- 関数ポインタ ----
def plus(i, j):
  return i + j
def minus(i, j):
  return i - j
FuncType = CFUNCTYPE(c_int, c_int, c_int)
plus_func = FuncType(plus)
result = test.set_calc_func(plus_func)
print result # ok c_long(125)
minus_func = FuncType(minus)
result = test.set_calc_func(minus_func)
print result # ok c_long(75)

# End of file


================================

大体、必要なことは、できそう。

2009年8月2日日曜日

C++アプリケーションからPythonのクラスのメンバ関数を呼び出す

C++アプリケーションからPythonの関数を呼び出すの続きで、Python内のクラスと、メンバ関数を呼び出します。

---- コード(Python側) testmodule.py ----

# -*- coding: utf-8 -*-

class TestClass:
  value = 10
  def testmethodplus(self, i, j):
    return i + j + self.value;

---- コード(C++側) ----
// ---- main ----
int main(int argc, char *argv[])
{
 const char* moduleName = "testmodule";
 const char* className = "TestClass";
 char* funcName = "testmethodplus";
 Py_Initialize();

 PyObject* pyModule = NULL;
 PyObject* pyClass = NULL;
 PyObject* pyInstance = NULL;
 PyObject* pyResult = NULL;
 try {
  PyObject* pyModuleName = PyString_FromString(moduleName);
  pyModule = PyImport_Import(pyModuleName);
  Py_DECREF(pyModuleName);
  // module呼び出し失敗時に NULL が返る
  if (NULL == pyModule) {
   throw PythonException();
  }
  // 以下、二つは、"借りた参照"
  PyObject* pyDict = PyModule_GetDict(pyModule);
  pyClass = PyDict_GetItemString(pyDict, className);

  // --- クラスをcall ---

  // 呼び出し不可オブジェクトの場合、0 が返る
  if (0 == PyCallable_Check(pyClass)) {
   // --- エラー処理 ---
   throw PythonException();
  }

  // クラスの実体を取得
  pyInstance = PyObject_CallObject(pyClass, NULL);

  // --- メンバ関数をcall ---

  if (NULL == pyInstance) {
   // --- エラー処理 ---
   throw PythonException();
  }
  pyResult = PyObject_CallMethod(pyInstance, funcName, "(ii)", 2, 5);

  if (NULL == pyResult) {
   // --- エラー処理 ---
   throw PythonException();
  }
  std::cout << "Result: "
   << PyInt_AsLong(pyResult) << std::endl;
 } catch (const PythonException& pyEx) {
  if (PyErr_Occurred()) {
   PyErr_Print();
  }
  std::cout << pyEx.what() << std::endl;
 }
 Py_XDECREF(pyResult);
 Py_XDECREF(pyInstance);
 Py_XDECREF(pyModule);

 Py_Finalize();
 return 0;
}

// End of file

前回のPythonの関数呼び出しのソースを流用していますが、関数呼び出しの部分が、 クラスcall→クラスの実体を取得→メンバ関数call となっている部分が異なります。
また、メンバ関数callで、 PyObject_CallMethod() 関数を使用しているため、引数の指定の仕方が違います。(PyObject_CallMethod()関数だけではなく、ほかにも方法はあります。)

クラスの実体を取得する際に使用した PyObject_CallObject() 関数は、"新たな参照"を返すため、 Py_DECREF() することを忘れないように...。

2009年8月1日土曜日

C++アプリケーションからPythonの関数を呼び出す

ここのソースをちょこっと改造しただけ。

---- コード(Python側) testmodule.py ----

# -*- coding: utf-8 -*-

def testplus(i, j):
return i + j + 3

---- コード(C++側) ----

#include <Python.h>
#include <iostream>
#include <string>
#include <sstream>

#define VARNAME(var) #var
// ---- exception ----
class PythonException
{
protected:
 const char* message_;
 const char* varName_;
public:
 static const char* errorMessge_ImportModule;
 static const char* errorMessage_GetObject;
 static const char* errorMessage_CallFunction;
 static const char* errorMessage_CreateTuple;
 PythonException(const char* message, const char* varName)
    : message_(message), varName_(varName) {}
 virtual ~PythonException() throw() {}
 virtual std::string what() const throw() {
  std::stringstream ss;
  ss << message_ << " (" << varName_ << ")";
  return ss.str();
 }
};
const char* PythonException::errorMessge_ImportModule
 = "Module import error.";
const char* PythonException::errorMessage_GetObject
 = "Get object error.";
const char* PythonException::errorMessage_CallFunction
 = "Call function error.";
const char* PythonException::errorMessage_CreateTuple
 = "Create tuple error.";

// ---- main ----
int main(int argc, char *argv[])
{
 const char* moduleName = "testmodule";
 const char* funcName = "testplus";
 Py_Initialize();

 PyObject* pyModule = NULL;
 PyObject* pyFunc = NULL;
 PyObject* pyArgs = NULL;
 PyObject* pyArg1 = NULL;
 PyObject* pyArg2 = NULL;
 PyObject* pyResult = NULL;
 try {
  PyObject* pyModuleName = PyString_FromString(moduleName);
  pyModule = PyImport_Import(pyModuleName);
  Py_DECREF(pyModuleName);
  // module呼び出し失敗時に NULL が返る
  if (NULL == pyModule) {
   throw PythonException(
    PythonException::errorMessge_ImportModule,
    VARNAME(pyModule));
  }
  // 以下、二つは、"借りた参照"
  PyObject* pyDict = PyModule_GetDict(pyModule);
  pyFunc = PyDict_GetItemString(pyDict, funcName);

  // PyObject_GetAttrString()の場合、
  // "新たな参照"のため、DECREFしないといけない
  //PyObject* pyFunc = PyObject_GetAttrString(pyModule, funcName);

  // 取得失敗時に NULL が返る(PyModule_GetDict()は失敗しない)
  if (NULL == pyFunc) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyFunc));
  }
  // 呼び出し不可オブジェクトの場合、0 が返る
  if (0 == PyCallable_Check(pyFunc)) {
   throw PythonException(
    PythonException::errorMessage_CallFunction,
    VARNAME(pyFunc));
  }

  // 以下、失敗時には NULL が返る
  pyArgs = PyTuple_New(2);
  if (NULL == pyArgs) {
   throw PythonException(
    PythonException::errorMessage_CreateTuple,
    VARNAME(pyArgs));
  }
  PyObject* pyArg1 = PyInt_FromLong(2);
  if (NULL == pyArg1) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyArg1));
  }
  PyTuple_SetItem(pyArgs, 0, pyArg1);
  PyObject* pyArg2 = PyInt_FromLong(5);
  if (NULL == pyArg2) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyArg2));
  }
  PyTuple_SetItem(pyArgs, 1, pyArg2);
  pyResult = PyObject_CallObject(pyFunc, pyArgs);
  if (NULL == pyResult) {
   throw PythonException(
    PythonException::errorMessage_GetObject,
    VARNAME(pyResult));
  }
  std::cout << "Result: "
   << PyInt_AsLong(pyResult) << std::endl;
 } catch (const PythonException& pyEx) {
  if (PyErr_Occurred()) {
   PyErr_Print();
  }
  std::cout << pyEx.what() << std::endl;
 }
 Py_XDECREF(pyResult);
 Py_XDECREF(pyArg2);
 Py_XDECREF(pyArg1);
 Py_XDECREF(pyArgs);
 //Py_XDECREF(pyFunc); // PyObject_GetAttrString()の場合
 Py_XDECREF(pyModule);

 Py_Finalize();
 return 0;
}

// End of file

コードは、ほぼ真似なので...(^^; 気をつけなければいけないことは、参照カウンタの操作です。 Python/C API関数を使用した際、戻り値の PyObject* が"新たな参照" の場合、 Py_DECREF() し、"借りた参照" の場合は、Py_DECREF()してはいけません。 これらについては、Python/C API リファレンスマニュアルで、それぞれの関数の戻り値を調べれば、わかるかと思います。

上記の例では、モジュールから、関数を取得する場合に、PyModule_GetDict()と、PyDict_GetItemString()を使用しています。
この二つは、"借りた参照"を返すため、戻り値の PyObject* を Py_DECREF() していません。
しかし、PyObject_GetAttrString() でも関数を取得できますが、こちらは、"新たな参照"を返すため、Py_DECREF() をします。そうしないと、メモリにごみが残ります。
参照カウンタの詳しい説明は、以下などを見ればよいかと思います。
GCアルゴリズム詳細解説
Py_DECREF() には、以下の2種類があります。
Py_DECREF() は、参照カウンタを1デクリメントします。
Py_XDECREF() は、NULLオブジェクトの場合、Py_DECREF()しません。NULLかどうかわからない場合は、こちらを使用したほうがよいでしょう。

2009年6月16日火曜日

Python setattr()でクラス内の関数を設定する

setattr()を使用することで、属性の設定ができるので、それを利用して、クラス内の関数の変更方法などを調べてみました。

---- コード ----

# -*- coding: utf-8 -*-

class Calc:
  data = 10
  def run(self, a, b):
    return a + b

def minus(a, b):
  return a - b

def plus(self, a, b):
  return a + b + self.data

def main():
  calc = Calc()
  print calc.run(1, 2)    # 3 を出力

  calc_minus = Calc()
  setattr(calc_minus, "run", minus)

  print calc.run(1, 2)    # 3 を出力
  print calc_minus.run(1, 2) # -1 を出力

  setattr(Calc, "run", plus)
  calc_plus = Calc()
  print calc.run(1, 2)    # 13 を出力
  print calc_minus.run(1, 2) # -1 を出力
  print calc_plus.run(1, 2)  # 13 を出力

if __name__ == "__main__":
  main()

================================

コードの例の calc_minus のように setattr(インスタンス, "属性名", 関数) とすることによって、関数をインスタンスに設定することは可能だが、その関数では、メンバ(属性)を利用することができない。
calc_plus のように、setattr(クラスオブジェクト, "属性名", 関数) とすることによって、メンバ(属性)を関数で使用することができる。

なお、関数のアドレスは、
 print calc.run
 print Calc.run
で確認することが可能です。

2009年6月12日金曜日

pythonでプロパティ

pythonでプロパティを使用する例。

---- コード ----

# -*- coding: utf-8 -*-

class Coordinate:
  _coord = [0.0, 0.0, 0.0] # x, y, z

  def __init__(self, x = 0.0, y = 0.0, z = 0.0):
    self._coord[0], self._coord[1], self._coord[2] = x, y, z

  def getx(self):
    return self._coord[0]
  def setx(self, value):
    self._coord[0] = value
  x = property(getx, setx)
  def gety(self):
    return self._coord[1]
  def sety(self, value):
    self._coord[1] = value
  y = property(gety, sety)
  def getz(self):
    return self._coord[2]
  def setz(self, value):
    self._coord[2] = value
  z = property(getz, setz)

coord = Coordinate(11.2, 12.3)
coord.x = 15.0
coord.y = 12.8

if __name__ == "__main__":
  print coord.x, coord.y, coord.z

# end of file

2009年5月22日金曜日

C# + Python embedding C#のクラスをPython側で使用する

C#のクラスをIronPython側で使うのは、非常に簡単だ。
(CPythonだと、結構、面倒だったような...)

---- コード(C#側) ----

using System;

using IronPython;
using IronPython.Hosting;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

namespace python_test001
{
 class Program
 {
  public static void Main(string[] args)
  {
   try
   {
    ScriptEngine engine = Python.CreateEngine();
    ScriptScope scope = engine.CreateScope();

    // C#と変数のやり取りをするPythonのファイルを設定
    ScriptSource source =
     engine.CreateScriptSourceFromFile("test.py");

    TestNumber test = new TestNumber();
    // Python側の変数をTestDataとする
    scope.SetVariable("num", test);

    // 実行
    source.Execute(scope);

    int i = test.Number;

    // 実行後のPython側の変数値を取得

    // 結果を出力 ==> 15
    System.Console.WriteLine(test.Number);
   }
   catch (Exception ex)
   {
    System.Console.WriteLine("Error:"
     + ex.Message.ToString());
   }
  }
 }
}

// Python側に渡すクラス
class TestNumber {
 int number = 0;
 public void Plus(int i) {
  number += i;
 }
 public int GetNumber() {
  return this.number;
 }
 public void SetNumber(int i) {
  this.number = i;
 }
 public int Number {
  get {
   return this.number;
  }
  set {
   this.number = value;
  }
  
 }
}

---- コード(Python側) ----

num.Number = 10
print 'py_number:', num.GetNumber()
#プロパティのgetがprotectedメンバだとかでエラー
#print 'py_number:', num.Number
num.Plus(5)

================================

■参考
貧脚レーサーのサボり日記 - C#でIronPython2.0をホスティングする
Embedding the Dynamic Language Runtime
The IronPython Calculator and the Evaluator

2009年5月20日水曜日

boost spirit ファイル解析エラー時情報

boost spirit でファイルを解析したときのエラー時情報出力の仕方について簡単な例があまりないようなので、調べたことを記述します。

下記のコードでは、1行にINT, REAL というキーワードと値の組み合わせを含んだファイルを読み込み、文法にエラーが存在した場合には、該当個所を出力します。

(正常な文法のファイルの例)
INT 100
REAL 10.0
INT 5
REAL 7.5

---- コード ----

#include <boost/spirit.hpp>
#include <iostream>
#include <iomanip>
#include <string>

// エラー出力のためのパーサ
struct error_parser {
 typedef boost::spirit::nil_t result_t;

 std::string msg_;
 error_parser(const char* msg) : msg_(msg) {}

 template <typename ScannerT>
 int operator()(const ScannerT& scan,
         result_t& result) const {
  if (!scan.at_end()) { // 入力が最後でない場合

   // スキャナの first には、ポジションイテレータが
   // 保持される
   boost::spirit::file_position fpos
    = scan.first.get_position();
   std::cout
    << "Error: " << fpos.file << " "
    << "Line("  << fpos.line  << ") "
    << "Column(" << fpos.column << ") "
    << msg_
    << std::endl;
  }
  return (-1);
 }
};
typedef boost::spirit::functor_parser<error_parser> error_p;
error_p syntax_error_p("Syntax is wrong.");
error_p keyword_error_p("Invalid Keyword.");
error_p value_error_p("Invalid Value.");

// 文法定義
struct test_grammar : boost::spirit::grammar<test_grammar> {
 template <typename ScannerT>
 struct definition {
  typedef boost::spirit::rule<ScannerT> rule_t;

  rule_t top_, line_,
      integer_statement_, real_statement_, other_statement_;

  definition(const test_grammar& self) {
   using namespace boost::spirit;

   top_ =
    +line_ | syntax_error_p;

   line_ =
    *blank_p >> (integer_statement_ |
           real_statement_  |
           other_statement_) >>
    ((*blank_p >> eol_p)               |
     ((anychar_p - blank_p - eol_p) >> value_error_p) |
     (+blank_p >> ((anychar_p - eol_p) >> syntax_error_p)));

   // "INT (整数)"
   integer_statement_ =
    str_p("INT") >>
    +blank_p >>
    (int_p | (anychar_p >> value_error_p));

   // "REAL (実数)"
   real_statement_ =
    (str_p("REAL") | keyword_error_p) >>
    +blank_p >>
    (real_p | (anychar_p >> value_error_p));

   // "それ以外"
   other_statement_ =
    anychar_p >> syntax_error_p;
  }
  const rule_t& start() const { return top_; }
 };
};

int main(int argc, char** argv) {
 std::string file = "sample.txt";

 // ファイルイテレータ
 typedef boost::spirit::file_iterator<> file_iterator_t;
 file_iterator_t file_first(file);
 if (!file_first) {
  std::cout << "cannot open file: " << file << std::endl;
  return (1);
 }

 // ポジションイテレータ
 typedef boost::spirit::position_iterator<file_iterator_t> position_iterator_t;
 position_iterator_t position_first(file_first,
                   file_first.make_end(),
                   file);
 position_iterator_t position_last;

 boost::spirit::parse_info<position_iterator_t> info =
  boost::spirit::parse(position_first,
             position_last,
             test_grammar());

 std::cout << std::boolalpha << info.full << std::endl;
 return (0);
}

================================

parse()関数の前に、ファイルイテレータ、位置情報イテレータを作成します。
ポジションイテレータは、 error_parser 内の operator()()関数内のscanで利用できます。

error_parserは、functor_parser<>によって、他の定義済パーサと同様にエラーパーサとして利用できるようにします。
definition()内にルールを記述する際、文法エラーの発生させたい個所に、エラーパーサを使用します。

尚、上記のコードでは、エラーの種類に応じて、出力メッセージを変更できるようにしています。

■参考
Spirit v1.6.0 - ファンクタパーサ
Spirit v1.6.0 - 詳細:スキャナ
boost ライブラリを使ってみる - エラー処理