C#/.net程序调用python
<h1 id="cnet程序调用python">C#/.net程序调用python</h1><p>C#的优势在于window下的开发,不仅功能强大而且开发周期短。而python则有众多的第三方库,可以避免自己造轮子,利用C#来做界面,而具体实现使用python来实现可以大大提高开发效率。本文介绍如何使用<code>pythonnet</code>来执行python脚本,使用<code>pythonnet</code>既可以具有较高的交互性,又可以使用第三方python库,同时可以将程序需要的python环境及第三方库打包到软件中,避免用户进行python的环境配置。</p>
<h2 id="c调用python的常见方法">C#调用python的常见方法</h2>
<p>调用python常见的方法有4种</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>优点</th>
<th>缺点</th>
</tr>
</thead>
<tbody>
<tr>
<td>使用IronPython</td>
<td>无需安装python运行环境,交互性强,C#和python无缝连接</td>
<td>某些python第三方库不支持,如numpy</td>
</tr>
<tr>
<td>使用C++调用Python,然后将C++程序做成动态链接库</td>
<td>交互性较强</td>
<td>需要用户配置Python环境,实现方式复杂</td>
</tr>
<tr>
<td>利用C#命令行调用py文件</td>
<td>执行速度快</td>
<td>需要用户配置Python环境,交互性差</td>
</tr>
<tr>
<td>将python文件打包成exe进行调用</td>
<td>无需安装python运行环境,</td>
<td>执行速度慢,传递数据复杂,交互性差</td>
</tr>
</tbody>
</table>
<p>可以看出4种方式均有限制,很难同时满足交互性强、可调用第三方python库、无需用户配置Python环境要求,而这几项要求恰恰是一款成熟软件所必须的。而使用<code>pythonnet</code>库可满足以上三点要求。</p>
<p><strong>本文均在.net 6环境下测试</strong></p>
<h2 id="使用pythonnet">使用pythonnet</h2>
<ol>
<li>
<p>Nuget安装<code>pythonnet</code></p>
</li>
<li>
<p>设置<code>Runtime.PythonDLL</code>属性,即pythonxx.dll路径,xx为版本号</p>
</li>
<li>
<p>设置<code>PythonEngine.PythonHome</code>,即python.exe所在路径</p>
</li>
<li>
<p>设置<code>PythonEngine.PythonPath</code>,python脚本所在目录,可以放置多个路径,以分号隔开,但是pathToVirtualEnv\Lib\site-packages和pathToVirtualEnv\Lib应放在最后</p>
</li>
<li>
<p>调用<code>PythonEngine.Initialize();</code></p>
<pre><code class="language-csharp">string pathToVirtualEnv = ".\\envs\\pythonnetTest";
Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
PythonEngine.Initialize();
//调用无参无返回值方法
using (Py.GIL()) //执行python的调用应该放在using (Py.GIL())块内
{
//python对象应声明为dynamic类型
dynamic np = Py.Import("test");
np.hello();
}
//调用有参有返回值方法
using (Py.GIL())
{
dynamic np = Py.Import("test");
int r = np.add(1, 2);
Console.WriteLine($"计算结果{r}");
}
</code></pre>
</li>
</ol>
<p>python文件,必须放在<code>PythonEngine.PythonPath</code>设定的目录下</p>
<pre><code class="language-python">def hello():
print("hello")
def add(a,b):
return a+b
</code></pre>
<h2 id="嵌入python环境及使用第三方库">嵌入Python环境及使用第三方库</h2>
<p>程序中包含Python脚本所需要的所有环境以及第三方库可以免去用户的自定义配置。本文使用Anaconda来构建专用的虚拟环境。</p>
<ol>
<li>
<p>创建专用虚拟环境(windows下首先切换到要建立虚拟环境的根目录下),执行<code>conda create --prefix=F:\condaenv\env_name python=3.7 </code>路径及python版本根据需要自定义。</p>
</li>
<li>
<p>使用Anaconda Prompt,激活虚拟环境<code>conda activate F:\condaenv\env_name</code></p>
</li>
<li>
<p>本次测试第三方库Numpy(如果需要其他库,安装方法相同),安装Numpy<code>pip install numpy</code></p>
<pre><code class="language-csharp">string pathToVirtualEnv = ".\\envs\\pythonnetTest";
Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
PythonEngine.Initialize()
//使用第三方库
using (Py.GIL())
{
dynamic np = Py.Import("numpy");
Console.WriteLine(np.cos(np.pi * 2));
dynamic sin = np.sin;
Console.WriteLine(sin(5));
double c = (double)(np.cos(5) + sin(5));
Console.WriteLine(c);
dynamic a = np.array(new List<float> { 1, 2, 3 });
Console.WriteLine(a.dtype);
dynamic b = np.array(new List<float> { 6, 5, 4 }, dtype: np.int32);
Console.WriteLine(b.dtype);
Console.WriteLine(a * b);
Console.ReadKey();
}
</code></pre>
<p><img src="https://qsnnimage.oss-cn-beijing.aliyuncs.com/img/202303011232978.png"></p>
<p><strong><code>注意:C#和python对象进行数学运算时,必须将Python对象放到前面,例如np.pi*2,不能是2*np.pi</code></strong></p>
</li>
</ol>
<h2 id="传递对象">传递对象</h2>
<p>可以将C#对象传递到python中</p>
<p>在C#中定义对象</p>
<pre><code class="language-csharp">public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
</code></pre>
<pre><code class="language-csharp">string pathToVirtualEnv = ".\\envs\\pythonnetTest";
Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
PythonEngine.Initialize();
//将C#中定义的类型传入python
using (Py.GIL())
{
Person p = new Person("John", "Smith");
PyObject pyPerson = p.ToPython();
string r1 = test.FullName(pyPerson);
Console.WriteLine($"全名:{r1}");
}
</code></pre>
<p>python脚本</p>
<pre><code class="language-python">def FullName(p):
return p.FirstName+""+p.LastName
</code></pre>
<p><img src="https://qsnnimage.oss-cn-beijing.aliyuncs.com/img/202303011409422.png"></p>
<h2 id="调用pyd文件">调用pyd文件</h2>
<p>pyd文件主要有以下2点作用:</p>
<ol>
<li>安全性更高:通过pyd生成的文件,已变成了dll文件,无法查看源码</li>
<li>编译成pyd后,性能会有提升</li>
</ol>
<p>将.py文件编译成pyd文件步骤如下:</p>
<ol>
<li><code>pip install cython</code></li>
<li>在.py文件目录下创建setup.py文件</li>
</ol>
<pre><code class="language-python">from distutils.core import setup
from Cython.Build import cythonize
setup(
name = "testName",
ext_modules = cythonize("test.py"), #将test.py文件编译成pyd
)
</code></pre>
<ol start="3">
<li>执行编译命令</li>
</ol>
<p><code>python setup.py build_ext --inplace</code></p>
<p>最后生成的pyd文件一般是test+cpython版本-平台为文件名,可以重命名为test名称,也可以不管,使用时仍然可以按test调用。</p>
<p>调动pyd文件和调用py文件相同,但是执行效率大大增强,下文会对执行速度进行对比。</p>
<h2 id="执行速度对比">执行速度对比</h2>
<p>在test.py中定义一个耗时函数</p>
<pre><code class="language-python">import time
def Count():
start = time.perf_counter()
sum = 0
for i in range(10000):
for j in range(10000):
sum = sum + i + j
print("sum = ", sum)
end = time.perf_counter()
runTime = end - start
runTime_ms = runTime * 1000
print("运行时间:", runTime, "秒")
</code></pre>
<ul>
<li>直接执行test.py脚本,运行结果如下:</li>
</ul>
<p><img src="https://qsnnimage.oss-cn-beijing.aliyuncs.com/img/202303011444644.png"></p>
<ul>
<li>在C#中调用Conut()函数</li>
</ul>
<pre><code class="language-csharp">//运行时间测试
Console.WriteLine("C#开始计时");
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
test.Count();
stopWatch.Stop();
Console.WriteLine($"C#计时结束{stopWatch.ElapsedMilliseconds}");
</code></pre>
<p>执行结果如下:</p>
<p><img src="https://qsnnimage.oss-cn-beijing.aliyuncs.com/img/202303011449504.png"></p>
<p>可以看到,使用pythonnet调用python脚本会有一定的性能损失,不过在对性能要求不是十分高的条件下是可以接受的。</p>
<ul>
<li>执行test.pyd文件,运行结果如下:</li>
</ul>
<p><img src="https://qsnnimage.oss-cn-beijing.aliyuncs.com/img/202303011451472.png"></p>
<p>从结果可以看出调用pyd比原生的py文件执行还要快,所以可以使用pythonnet来执行pyd文件,即实现代码保护又提升了执行效率。</p><br><br>
来源:https://www.cnblogs.com/qsnn/p/17168273.html
頁:
[1]