Python与DotNet交互环境的搭建以及一些问题

之前一直工作在.NET环境下使用C#,在稍微接触了Python以后被迷住了,实际上到现在除了一个演示滤波的小玩具其他什么也没做,不过参考之前的一些经历,觉得把环境搭好比较重要,方便以后随时地开始玩(造)。
在经历了开始的兴奋阶段后,开始琢磨我可以用这个做些什么,当然对我日常工作很有帮助,但是鉴于我的工作大部分时间要在客户的工作站上完成,所以有了以下需求:
1.需要打包成exe部署到客户端。
2.需要可以和C#交互以便复用之前的一些工作,避免重复开发引起的功能不兼容,以及使用对方没有的功能。

解决方案有3个(未完全测试):

1.IronPython,在.NET下实现Python,这样可以在Python中使用所有的.NET库,当然支持所有Python语法。
2.Microsoft Scripting功能以及IronPython.dll,实际上是在.NET程序中加入了脚本支持功能。
3.CPython+Python for .NET,实际上通过一个接口可以在Python中调用.NET下的CLR,最后再用py2exe打包。

先声明:环境搭建好后还没有经过完全测试,没办法保证可以部署到其他设备上正常使用。会慢慢找机会测试。

方案一——将IronPython集成到.NET开发环境:

首先安装IronPython,安装好后可以使用自带的或其他IDE,我试了Eclipse+PyDev,和SharpDevelop,PyDev不支持可视化图形界面,而SharpDevelop没办法加BreakPoint(不知道是不是我不会设置,求大神帮助)。

一个简单的例子如下:

import sys
sys.path.append("C:\Program Files (x86)\IronPython 2.7\Lib")
sys.path.append("C:\\Program Files (x86)\\IronPython 2.7\\lib\\site-packages")
sys.path.append(r"C:\Program Files (x86)\IronPython 2.7")
sys.path.append(r"C:\Program Files (x86)\IronPython 2.7\DLLs");
sys.path.append(r"C:\Python27\Lib\site-packages")
import clr
clr.AddReference('mtrand.dll')
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
#clr.AddReference('SPInterface')
clr.AddReference('System.IO')
clr.AddReference('System')


from System.Diagnostics import Trace
from System import Console
#import SPInterface as SP
import numpy as np

print 'Hello World'

如果有多种语言的经验,这里比较好理解
sys.path.append类似C编译时的头文件Include路径
clr.AddReference就是.NET里的添加引用
而from System.Diagnostics import Trace则是类似于C#里的
using System.Diagnostics

里面注释掉的几行是我在C#之前写的库,经测试可以正常使用(而且我的库依赖mathdotnet,在同文件夹下貌似自动识别了)。

优点:比较方便,更偏向于.NET,虽然使用的是Python语法。可以很方便的搭建界面。

缺点:由于不兼容CPython,所以很多库用不了,numpy刚刚支持没多久,而matplotlib不支持,实际上感觉完全不能发挥Python的优点。

方案二——在.NET开发环境使用Python脚本:

第一步同上,安装IronPython,在安装文件夹下有Platforms\Net40文件夹,在.NET项目中引用其中的Microsoft.Scripting.dll和IronPython.dll,可以去网上搜搜例子,就可以在代码调用Python脚本了,一个简单的例子:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using IronPython.Hosting;

namespace testIron2
{
   public partial class Form1 : Form
    {
        Microsoft.Scripting.Hosting.ScriptEngine engine;
        public Form1()
        {
            InitializeComponent();
            var source =
@"
import clr
clr.AddReference('mtrand.dll')

def SysPath():
    import sys
    return sys.path
def ImportNumpy():
    import numpy
            ";

            engine = Python.CreateEngine();

            var sp = engine.GetSearchPaths();
            sp.Add(@"C:\Program Files (x86)\IronPython 2.7");
            sp.Add(@"C:\Program Files (x86)\IronPython 2.7\DLLs");
            sp.Add(@"C:\Program Files (x86)\IronPython 2.7\Lib");
            sp.Add(@"C:\Program Files (x86)\IronPython 2.7\Lib\site-packages");
            engine.SetSearchPaths(sp);

            var scope = engine.CreateScope();
            var ops = engine.Operations;

            engine.Execute(source, scope);
            dynamic py = scope;

            // Calling into IronPython works fine + sys.path looks correct
            dynamic syspath = py.SysPath();

            // EXCEPTION when we try to "import numpy":
            // "The type initializer for 'NumpyDotNet.NpyCoreApi' threw an exception.",
            // "Unable to load DLL 'NpyAccessLib': The specified module could not be found. (Exception from HRESULT: 0x8007007E)"
            dynamic numpy = py.ImportNumpy();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            engine.Execute(textBox1.Text);
        }
    }
}

这段代码是当时在网上找的,很抱歉忘记原始链接了,里面的注释应该是原作者遇到的问题,我遇到了应该是相同的,异常信息是无法读取库,原因是没有mtrand.dll文件,stack overflow上有解答,貌似是因为C编译库的问题,所以要在Python命令字符串中加入

clr.AddReference('mtrand.dll')

可以看出来,和方案一其实是一样的,所以优点是熟悉C#代码的可以尽量的用C#,脚本只是辅助方便现场调试修改。
貌似所有在IronPython下安装的库会有DLL版,这意味着我在纯C#下可以用numpy了?还没有测试。

缺点:所有方案一的缺点,而且Python代码无法调试,只有看异常信息然后从头运行。

方案三:在有.NET环境下安装Python for .NET

这个是目前看起来最方便的,只需要装一个库,就可以用类似IronPython的方式来调用.NET环境,包括我自己的库。同时保留所有之前的功能。简单例子如下:

# encoding: utf-8
'''
Created on 2014年5月14日

@author: zcxsun
'''
# import sys
#sys.path.append(r"C:\Qt\Projects\sp_reader\sp_reader\bin\Debug")

import clr
# clr.AddReference('SPInterface')

from System import String
#import SPInterface as SP

a = String('   a,b,c,d,e,f,g   ')
b = a.Trim()
c = String(b).Split(',')
for i in c:
    print i
# SP.SP_Path.folder_name = r'C:\Users\Public\Documents\Zeiss\CALYPSO\sp_filter\conf\spiConf.xml'
# SP.SPI.init()
# for i in SP.SPI.elements:
#     print i.identifier

print 'finish'

注释部分是调用我自己库的代码,需要注意的是很多地方需要显式转换,所以不能用类似a.Trim().Split(‘,’)这样的语法,只能String(a.Trim()).Split(‘,’)这样。因为用type(String(‘ a,b,c ‘).Trim())得到的类型不是Trim应该返回的String类型,而是<type ‘unicode’>

这个感觉应该是最方便的,在Eclipse下显示良好,调试良好。不确定的是py2exe后效果如何。

结论:目前感觉方案2针对我的情况是最优的,方案3则是用来实现功能测试用最方便的,所以未来的短期计划是用PyDev调用一些自己的库,用来设计和实现算法和功能。实验好了后在C#中实现,然后将脚本功能放在文件中调用。

当然,这样的性能应该是有一定问题的,不过对于我的情况是够了。

 

使用Python模拟高斯滤波过程并生成视频

Python在这类工作上的方便程度真的是——爽。

这里模拟的是低通高斯滤波的过程。

代码如下:需要numpy 和matplotlib,如果想保存成视频文件还需要下载ffmpeg

# encoding: utf-8
'''
Created on 2014年5月1日

@author: zcxsun
'''
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
plt.rcParams['animation.ffmpeg_path'] = r'C:\sunxin\ffmpeg_x64\bin\ffmpeg.exe'

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()

speed = 1
size = 360
a = np.arange(size) *np.pi/180
b = np.zeros(size)
x = -0.1
for i in range(1,14,2):
    x = x*(i-2)/i
    b += x * np.sin(a*i)
b+= 0.02 * np.sin(a*50) + 0.01 * np.sin(a*150)
fix_signal = b

hamonic = 15

len = max(1, size/hamonic/2)
print len
scale = 0.3*len+0.8;
scale2x = -0.5/scale/scale
win =  np.exp( pow(np.arange(2*len+1)-len,2)*scale2x)
win/=sum(win)
move_gauss = win
print win.size
filtered_data = np.convolve(b, win, 'same')


#set the axis arrange
y_max = 0.4
ax = plt.axes(xlim=(0, 360), ylim=(-0.2, y_max))
line_gauss, = ax.plot([], [], lw=2)
line_filtered, = ax.plot([],[],lw=2)

plt.plot(fix_signal)

# initialization function: plot the background of each frame
def init():
    line_gauss.set_data([],[])
    line_filtered.set_data([],[])
    return line_gauss,line_filtered,
# animation function.  This is called sequentially
def animate(i):
    n = np.floor(i*speed)
    line_gauss.set_data(n+np.arange(0,win.size),win + (y_max-0.03-win.max()))
    line_filtered.set_data(np.arange(0,n),filtered_data[0:n])
    return line_gauss,line_filtered,
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=int(np.floor(size/speed)), interval=10, blit=True)

FFwriter = animation.FFMpegWriter(fps = 15)
path = r"C:\Users\Public\Documents\filter_show_hamonic_" + str(hamonic) + ".mp4"
anim.save(path, writer = FFwriter)
#
# plt.show()
print 'finish'