IronPython を中継して、C#からPythonライブラリを使ってみる
- IronPython 1.0.1
Python の.NET実装 - pyExcelerator 0.6.3a
EXCEL ファイルを Python から読み書きするライブラリ
IronPython から Python ライブラリを使うことは難しくないということなのでやってみた。
それだけじゃ面白くないので、さらにC#を連携させてみたらなんとか動いたのでメモ。
- サンプル一式
一応 EXCELのない環境でも .NET2.0 があれば動くはず・・・(そんな環境あるのか怪しいけど)
ただし以下のような問題があって、解決方法が思い浮かばない。
- 日本語を含むと「System.Text.EncoderFallbackException」が発生する。ファイル操作自体は問題なさげ。
- pyExcelerator で __slots__ が定義されているクラスをIronPythonに読み込めない。 __slots__ を削除すると読み込める。(サンプルは削除済み)
- セルに書き込めるのは文字列のみ。
- セルに日付が入力されていても判定できない。(pyExcelerator で判定する方法が分からない)
- 初回起動が遅い。
手順としてはこんな感じで出来た。
1. C# でインターフェイスを定義
これはC#で作る普通のインターフェイス。
using System; using System.Collections.Generic; using System.Text; namespace IronXls { public interface IXls { int GetMaxCol(string sheetname); int GetMaxRow(string sheetname); IList<string> GetSheetnames();//string[] や List<string>だと駄目だった void Open(string path); string Read(int row, int col); string Read(int row, int col, string sheetname); void Save(string path); void Write(int row, int col, string value); void Write(int row, int col, string value, string sheetname); } }
2. IronPython で上記のインターフェイスを実装して Python ライブラリを使用
Python のクラス継承と同じ要領で class Xls(C#のインターフェイス) とすればいい。
あとは普通のPythonと同じようにする。
# -*- coding: utf-8 -*- import sys from pyExcelerator import * import IronXls class Xls(IronXls.IXls): def __init__(self, path=None): self._reset() if path: self.Open(path) def _reset(self): self.book = Workbook() self.sheets = {} self.matrix = {} self.maxnum = {} self.lastsheet = None def GetMaxCol(self, sheetname): if not self.maxnum.has_key(sheetname):return -1 row, col = self.maxnum[sheetname] return col def GetMaxRow(self, sheetname): if not self.maxnum.has_key(sheetname):return -1 row, col = self.maxnum[sheetname] return row def GetSheetnames(self): return self.sheets.keys() def Open(self, path): self._reset() for sheetname , values in parse_xls(path): w = self.book.add_sheet(sheetname) self.sheets[sheetname] = w for row, col in values.keys(): self.Write(row, col, values[(row, col)], sheetname) def Read(self, row, col, sheetname=None): sheetname = self._getAccessSheet(sheetname) if not self.sheets.has_key(sheetname): raise Exception, u'sheet %s not found' % sheetname mrow, mcol = self.maxnum[sheetname] if row > mrow or col > mcol: raise Exception, u'(%d, %d) is out of range : sheet %s' % (row,col,sheetname) elif not self.matrix[sheetname].has_key((row, col)): return "" v = self.matrix[sheetname][(row, col)] if isinstance(v, str):#IronPython では Unicode 指定しても str になる return v else: return str(v) def Save(self, path): self.book.save(path) def Write(self, row, col, value, sheetname=None): sheetname = self._getAccessSheet(sheetname) if self.sheets.has_key(sheetname): w = self.sheets[sheetname] else: w = self.book.add_sheet(sheetname) self.sheets[sheetname] = w w.write(row, col, value) self.matrix.setdefault(sheetname, {})[(row, col)] = value mrow, mcol = self.maxnum.setdefault(sheetname,(-1,-1)) if mrow < row or mcol < col: self.maxnum[sheetname] = (max(mrow, row), max(mcol, col)) def _getAccessSheet(self, sheetname): if sheetname: self.lastsheet = sheetname return sheetname if not self.lastsheet: sheetname = "sheet1" elif self.lastsheet: sheetname = self.lastsheet self.lastsheet = sheetname return sheetname
4. C# で IronPython エンジンを生成して上記のスクリプトを読み込む
肝心なのはこんなところかな。
①Pythonライブラリのパスを指定
②中継用のIronPythonスクリプトの読み込み
③IronPythonスクリプトで定義したクラスの生成
using System; using System.IO; using System.Reflection; using IronPython.Hosting; namespace IronXls { public class XlsFactory { private static XlsFactory __instance = new XlsFactory(); private PythonEngine _engine; private XlsFactory() { } private PythonEngine Engine { get { if (_engine == null) { _engine = new PythonEngine(); Assembly a = this.GetType().Assembly; _engine.LoadAssembly(a); string path = Path.Combine(Path.GetDirectoryName(a.Location), "Scripts"); _engine.Sys.path.Append(path);//① _engine.ExecuteFile(Path.Combine(path, "bridge.py"));//② } return _engine; } } ~XlsFactory() { if (_engine != null) { _engine.Dispose(); } } public static IXls Create() { return __instance.Engine.EvaluateAs<IXls>("Xls()");//③ } public static IXls Create(string path) { return __instance.Engine.EvaluateAs<IXls>(string.Format("Xls(r'{0}')", path)); } } }