主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

IronPython: PythonでExcelマクロ

                
 大量のExcelデータをさばきたいとき、世間でのスタンダードはVBAだ。だけど個人的にWindowsアプリはC#を使っていて、ファイルコピーや削除などの操作はPythonでやってきていて、VBAを使う必要に迫られてこなかった。だからPython書けるならPythonでExcelマクロやっちゃおうと。
参考:http://www.ironpython.info/index.php?title=Interacting_with_Excel
参考と違うところ:コンポーネントオブジェクトモデル(COM)ではなくてグローバルアセンブリキャッシュ(GAC)を使おう

環境: Windows 7(64bit), .NET Framework 4, IronPython 2.7, Office 2007, VS2010

 まずIronPythonのスクリプト上でExcelのワークシートやらセルなどをオブジェクトとして使えるようにするための準備。参考ページで説明しているCOMを使う方法は若干めんどう。
import clr
clr.AddReferenceByName('Microsoft.Office.Interop.Excel, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c')

 バージョン番号が入ったりキートークンなるものも入ってるので、OSやExcelのバージョンが変わったらこの部分がエラー吐きそう。そもそもCOMのラッパーDLLも用意しなければならんはず。それがCOM。
 そこでGACを使う。GACとは?
”共通言語ランタイムがインストールされている各コンピューターには、グローバル アセンブリ キャッシュと呼ばれる、コンピューター全体にわたって使用されるコード キャッシュがあります。 グローバル アセンブリ キャッシュは、そのコンピューター上の複数のアプリケーションで共有するように指定されたアセンブリを格納します”
http://msdn.microsoft.com/ja-jp/library/yf1d93sz(v=vs.110).aspx
ラッパーDLLの用意とかいらない。ExcelのGACは以下のコードで使えるようになる。
import clr
clr.AddReference("Microsoft.Office.Interop.Excel")

色々めんどそうな名前や番号が外れて、Microsoft.Office.Interop.Excelというわりとシンプルな名前だけになった。ちなみにこれで読み込まれるDLLを調べたところ、VS2010を入れている私の環境ではVS2010のインストールディレクトリ下にあった。

 GACを使ってExcelのもろもろをオブジェクトとして使う準備ができた。あとはスクリプト書くだけ。セルにHello Absurd World!の文字を分解してつっこむスクリプトを書いてみた。ついでに保存もしている。
import System
import clr

clr.AddReference("Microsoft.Office.Interop.Excel")
import Microsoft.Office.Interop.Excel as Excel

CURRENT_DIR = System.IO.Directory.GetCurrentDirectory()

ex = Excel.ApplicationClass()
ex.Visible = True
ex.DisplayAlerts = False

#workbook = ex.Workbooks.Open('foo.xls')
workbook = ex.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet)
ws = workbook.Worksheets[1]
row = 1
col = 1
for ch in "Hello Absurd World!":
ws.Cells[row, col] = ch
if ch == " ":
row += 1
col = 1
else:
col += 1

ws.SaveAs(CURRENT_DIR + r"\foo.xls")

1407151402468.png
↑をexcel.pyで保存したとして、プロンプトから"[ironpython path]\ipy.exe excel.py"で実行。
Cells[row, col]でワークシート内のセルにアクセスできるが、rowやcolの値は1から。0からではない。
スポンサーサイト



IronPython: MongoDBを使う

                
 Pythonの.NET Framework開発であるIronPython。pystoneで計測するとCPythonを超えるパフォーマンスを出したりしながらも、.NET Frameworkで用意されたAPIを利用できるので、GUIアプリなどを即席で作ったりするのにけっこう使える。今回はMongoDBをIronPythonからいじってみる。


 CPythonで動かしていたアプリケーションをIronPythonに移そうとしたら、エラーが出て動かなかった。原因はMongoDBに接続するために使っていたPythonモジュールのPyMongo。調べてみるとPyMongoはIronPythonに対応していない。ここで出てくる選択肢は自分で開発するか諦めるか。しかしIronPythonならもう一つの手が出てくる、C#や.NET Framework向けに開発されたドライバ使えばいいじゃない。というわけでIronPythonからMongoDBに接続するためのドライバとしてC# Driverを使う。

 C#コードを参考に、IronPythonでドキュメント(RDBでいうところのレコード)の挿入、検索、削除をやってみる。
http://www.codeproject.com/Articles/273145/Using-MongoDB-with-the-Official-Csharp-Driver
不明瞭なところは公式ドキュメントを参考にしながら。
http://api.mongodb.org/csharp/current/

 ドライバのZIPを落として解凍し、Pythonコードと同じディレクトリにMongoDB.Driver.dllとMongoDB.Bson.dllを置いて、以下のようにコードを書けばMongoDBに接続できる(もちろんMongoDBを立ち上げた状態で)。
import clr
clr.AddReference("MongoDB.Bson")
clr.AddReference("MongoDB.Driver")

import MongoDB.Bson
import MongoDB.Driver


 ここでコーディングを容易にするため、補完機能を使う準備をする(しなくてもいい)。コーディングに使うIDEはSharpDevelop 4.4。Pythonプロジェクトを作成して、プロジェクトウィンドウの参照設定にMongoDB.Driver.dllとMongoDB.Bson.dllを追加。MongoDB.Driver.xmlとMongoDB.Bson.xmlも同じディレクトリにコピーしておく。これで補完機能が効くようになる。
1407021654079.png


 改めて下記のコードを。
import clr
clr.AddReference("MongoDB.Bson")
clr.AddReference("MongoDB.Driver")

import MongoDB.Bson
import MongoDB.Driver

これはDLLファイルへの参照を行ったあとで、その機能が使えるようにするためPythonのいつもどおりのimportを行っている。

 続いてデータベースへの接続をする。MongoDBをローカルで、ポートも初期設定で動かしていれば、とくにパラメータを渡すことなく接続できる。
client = MongoDB.Driver.MongoClient()
server = client.GetServer()
db = server.GetDatabase("test")
collection = db.GetCollection("foos")


 挿入をする。今回用意したC# Driverではドキュメントを、.NET Frameworkのディクショナリで表している。これはIronPythonで用意されているディクショナリを使ってもOK。なのでPythonの辞書型でドキュメントとして保存するデータを書く。
# insertion
for p in xrange(10):
foo = MongoDB.Bson.BsonDocument({"x":p, "y":p})
#foo = MongoDB.Bson.BsonDocument()
#foo["x"] = p
#foo["y"] = p
collection.Insert(foo)


 検索をする。QueryDocumentを使って条件を指定できる。
# no filter query
q = MongoDB.Driver.QueryDocument()
cols = collection.Find(q)
for col in cols:
print "no filter", col

# y==0
q.Add("y", 0)
cols = collection.Find(q)
for col in cols:
print "y==0", col

# y!=0
q = MongoDB.Driver.QueryDocument()
q.Add(MongoDB.Driver.Builders.Query.NE("y", 0))
cols = collection.Find(q)
for col in cols:
print "y!=0", col


 削除する。
collection.RemoveAll()
print collection.Count()


 C#のコードから型指定などを省けば、C#ドライバとして用意されていたものがほぼそのまま使える。
http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-csharp-driver/#getting-started-with-csharp-driver

 というわけでPythonのサードパーティモジュールが使えないような場合においても、C#向け、あるいは.NET Framework向けとされるライブラリを探して使えば抱えていた問題が解決される。意外と使えてしまうIronPython。

IronPython: IronPython(CLI) on Windows 8.1 preview

                
Windows 8をいくらか改良したWindows 8.1 previewが公開された。個人的な興味はIronPythonのような.NET Frameworkモノが動くかということ。というわけでVMware Playerに入れたWindows 8.1 previewでIronPythonが動作するかを確認してみた。

IronPythonは2.7.3のバイナリを用意し、Pythonのurllib2モジュールを使ったスクリプトを動かして、ネットワークアクセスしてドキュメントを取ってこれるかやってみた。結果はなんなく成功。
130628.jpg

今回の8.1 previewはWindows 8の改良ということで、スタートボタンの復活などUI面での改良が主なところらしい。となると、.NET FrameworkのようにWindows 8で動いていたものは心配無用なのだろう。


ユーザー名を適当につけたところ、スペルを間違えて恥ずかしいところだが。

IronPython, C#: Expression Encoder SDKを使ったスクリーンキャプチャアプリケーション

                
Windows向けスクリーンキャプチャアプリケーションを作った。今回はスクリプトとともに技術的なことをメモ。

13051201.png

画像のように、デスクトップ上の録画領域の位置を調整する際に、半透明でなにもコントロールのないウィンドウを表示することで、録画領域がどこかわかりやすくなるようにしている。この半透明領域はウィンドウだが、領域内でクリックをしてもスルーして、下のウィンドウにマウスイベントをパスするようになっている。この技術はC#で見つけることができた。
http://stackoverflow.com/questions/2842667/how-to-create-a-semi-transparent-window-in-wpf-that-allows-mouse-events-to-pass
C#でクラスを作成してdll化し、それをIronPythonから利用している。
using System;

using System.Windows.Interop;
using System.Runtime.InteropServices;

namespace WindowServices
{
public static class WindowsServices
{
const int WS_EX_TRANSPARENT = 0x00000020;
const int GWL_EXSTYLE = (-20);

[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

public static void SetWindowExTransparent(IntPtr hwnd)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
}
}


from System.Windows.Interop import *
from WindowsServices import WindowsServices

hwnd = WindowInteropHelper(window).Handle
WindowsServices.SetWindowExTransparent(hwnd)


Window1.py
import wpf, clr

clr.AddReference('System.Drawing')
clr.AddReference('Microsoft.Expression.Encoder')
clr.AddReference('WindowServices')

import System
from System.Drawing import *
from System.IO import *
from System.Threading import *
from System.Windows import *

from Microsoft.Expression.Encoder.ScreenCapture import *
from Microsoft.Expression.Encoder.Devices import *
from Microsoft.Expression.Encoder.Profiles import *
from System.Windows.Interop import *
from System.Runtime.InteropServices import *
from WindowServices import WindowsServices

class Window1 (Window):
def __init__(self):
wpf.LoadComponent(self, 'Window1.xaml')
self.textBoxX = self.FindName('textBoxX')
self.textBoxX.TextChanged += self.AdjustTransparentWindow
self.textBoxY = self.FindName('textBoxY')
self.textBoxY.TextChanged += self.AdjustTransparentWindow
self.textBoxWidth = self.FindName('textBoxWidth')
self.textBoxWidth.TextChanged += self.AdjustTransparentWindow
self.textBoxHeight = self.FindName('textBoxHeight')
self.textBoxHeight.TextChanged += self.AdjustTransparentWindow
self.recButton = self.FindName('RecButton')
self.stopButton = self.FindName('StopButton')

self.StateChanged += self.StateChange

self.job = ScreenCaptureJob()

self.Show()

self.tWindow = Window()
wpf.LoadComponent(self.tWindow, 'TransparentWindow.xaml')
self.tWindow.Show()
self.AdjustTransparentWindow()
hwnd = WindowInteropHelper(self.tWindow).Handle
WindowsServices.SetWindowExTransparent(hwnd)

def StateChange(self, target, e):
if self.WindowState == WindowState.Minimized:
self.tWindow.WindowState = WindowState.Minimized
elif self.WindowState == WindowState.Normal:
self.tWindow.WindowState = WindowState.Normal

def AdjustTransparentWindow(self, *args):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return

if (x0 < 0) or (y0 < 0) or (width < 0) or (height < 0):
return
self.tWindow.Left = x0
self.tWindow.Top = y0
self.tWindow.Width = width
self.tWindow.Height = height
self.tWindow.WindowState = System.Windows.WindowState.Normal

def OnClose(self, sender, e):
if (self.job.Status == RecordStatus.Running):
result = System.Windows.MessageBox.Show("Capturing in Progress. Are You Sure You Want To Quit?", "Capturing", MessageBoxButton.YesNo)
if (result == MessageBoxResult.No):
e.Cancel = True
return
self.job.Stop()
self.job.Dispose()
Application.Current.Shutdown()

def RecButton_Checked(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
width = width + (4 - width % 4)
height = height + (4 - height % 4)
if (x0 < 0) or (y0 < 0) or (width < 0) or (height < 0):
return
capRect = Rectangle(x0, y0, width, height)

audioDevices = EncoderDevices.FindDevices(EncoderDeviceType.Audio)
self.job.AddAudioDeviceSource(audioDevices[len(audioDevices)-1])
self.tWindow.Opacity = 0.0
self.job.ScreenCaptureVideoProfile.Bitrate = ConstantBitrate(300 * 10 ** 3)
self.job.CaptureRectangle = capRect
self.job.OutputPath = Directory.GetCurrentDirectory()
self.recButton.Width = 0
self.stopButton.Width = 112
self.job.Start()

def RecButton_UnChecked(self, sender, e):
self.job.Stop()
self.recButton.Width = 112
self.stopButton.Width = 0
self.tWindow.Opacity = 0.7

def IncreaseX(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
x0 += 1
self.textBoxX.Text = x0.ToString()
self.tWindow.Left = x0

def DecreaseX(self, sender, e):
try:
x0 = int(self.textBoxX.Text)
except ValueError:
return
x0 -= 1
self.textBoxX.Text = x0.ToString()
self.tWindow.Left = x0

def IncreaseY(self, sender, e):
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
y0 += 1
self.textBoxY.Text = y0.ToString()
self.tWindow.Top = y0

def DecreaseY(self, sender, e):
try:
y0 = int(self.textBoxY.Text)
except ValueError:
return
y0 -= 1
self.textBoxY.Text = y0.ToString()
self.tWindow.Top = y0

def IncreaseWidth(self, sender, e):
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
width += 1
self.textBoxWidth.Text = width.ToString()
self.tWindow.Width = width

def DecreaseWidth(self, sender, e):
try:
width = int(self.textBoxWidth.Text)
except ValueError:
return
width -= 1
self.textBoxWidth.Text = width.ToString()
self.tWindow.Width = width

def IncreaseHeight(self, sender, e):
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
height += 1
self.textBoxHeight.Text = height.ToString()
self.tWindow.Height = height

def DecreaseHeight(self, sender, e):
try:
height = int(self.textBoxHeight.Text)
except ValueError:
return
height -= 1
self.textBoxHeight.Text = height.ToString()
self.tWindow.Height = height


Window1.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Screen Capture"
Closing="OnClose"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Height="200"
Width="150"
WindowStartupLocation="Manual"
Top="150"
Left="700"
ResizeMode="CanMinimize"
HorizontalContentAlignment="Left">
<WrapPanel HorizontalAlignment="Center">
<Button
Name="RecButton"
Content="Rec Start"
Click="RecButton_Checked"
Height="30"
VerticalAlignment="Top"
Width="140" />
<Button
Name="StopButton"
Content="Stop"
Click="RecButton_UnChecked"
Height="30"
VerticalAlignment="Top"
Width="0" />
<TextBlock
Height="23"
Name="textBlock1"
Text="X"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseX" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxX"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="100" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseX" />
<TextBlock
Height="23"
Name="textBlock2"
Text="Y"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseY" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxY"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="100" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseY" />
<TextBlock
Height="23"
Name="textBlock3"
Text="Width"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseWidth" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxWidth"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="600" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseWidth" />
<TextBlock
Height="23"
Name="textBlock4"
Text="Height"
Width="50"
TextAlignment="Center" />
<Button
Content="-"
Width="13"
Height="23"
Click="DecreaseHeight" />
<TextBox
Height="22"
HorizontalAlignment="Left"
Name="textBoxHeight"
VerticalAlignment="Top"
Width="50"
TextAlignment="Right"
Text="360" />
<Button
Content="+"
Width="13"
Height="23"
Click="IncreaseHeight" />
</WrapPanel>
</Window>



TransparentWindow.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None"
Opacity="0.7"
ShowInTaskbar="False"
AllowsTransparency="True" Background="Black"
Topmost="True" WindowState="Normal"
WindowStartupLocation="Manual"
Left="0" Top="0">
</Window>


Application.py
import wpf

from System.Windows import Application
from Window1 import Window1

window = Window1()
app = Application()
app.Run(window)

IronPython: モジュールの依存スクリプトを集める

                
Python: Dependency of Python Modules

.NETやMonoでIronPythonを使うとき、Pythonモジュールを使うならそのスクリプトファイルが必要になる。EXEやDLLにするなら、スクリプトファイルまでコンパイルする必要がある。

モジュールのスクリプトはたいていが独立して実行できるわけではない。他のモジュールに依存している。この依存スクリプトファイルを集めるのが厄介。これを簡便化するために、モジュールを収集してくれるスクリプトを書いた。

使用はIronPythonが単独でインストールされたうえで、なおかつSharpDevelop上での実行を想定している。SharpDevelopはインストール時にIronPythonの実行のためのファイルが一式ついてくる。しかしPythonの標準モジュールのスクリプトは入っていない。SharpDevelopからIronPythonスクリプトを実行しようとすると、このモジュールなし環境での実行になる。そのためモジュールをインポートしようとすると、ImportErrorを吐く。このImportErrorを拾って、必要なモジュールを割り出している。必要なモジュールはSharpDevelopとは別でインストールされたIronPythonのフォルダから拾ってくるようにしている。

実行ディレクトリにLibというディレクトリを作る。IronPythonのLibディレクトリへのパスを入れる。欲しいモジュール名を入れる。異常の3つのステップ後に実行すれば、実行ディレクトリ下のLibディレクトリに必要スクリプトがそろえられている。

モジュールがスクリプトファイルで用意されている場合のみに対応しており、モジュールがディレクトリ構成を持っている場合は対応させていないので注意。

13041800.jpg

import sys
sys.path.append("Lib")

module_name = "os"
ipy_path = r"c:\IronPython 2.7\Lib\\"

d = module_name
copy_module = r"Lib\{0}.py"
li_module = []
importer = "import " + module_name
while d:
try:
__import__(module_name)
d = ""
except ImportError, err:
d = err[0].replace("No module named ", "")
li_module.append(d)
print d
filename = ipy_path + d + ".py"
with open(filename, "r") as f_read:
with open(copy_module.format(d), "w") as f_write:
f_write.write(f_read.read())
プロフィール

h

Author:h

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon