Введение
Интеграция скриптов Python в приложение на C# позволяет использовать обширные библиотеки Python, сохраняя при этом все преимущества строго типизированного языка C#. Это особенно полезно в ситуациях, когда необходимо выполнять сложные вычисления или анализ данных, для которых уже существуют мощные и хорошо зарекомендовавшие себя библиотеки Python, такие как pandas
или NumPy
. Вместо того чтобы разрабатывать новые API или сервисы для выполнения этих задач, можно непосредственно вызывать скрипты Python из C# кода, что упрощает процесс разработки и уменьшает время на интеграцию.
Этот гайд предоставляет пошаговое руководство по созданию приложения на C#, которое вызывает скрипт Python, использует виртуальное окружение для управления зависимостями Python и измеряет время выполнения скрипта. Такой подход позволяет комбинировать лучшие стороны обоих языков, обеспечивая эффективность и гибкость в разработке приложений.
Почему не IronPython и не Python.NET?
IronPython
IronPython — это реализация Python для .NET, которая позволяет выполнять код Python в среде .NET. Тем не менее, существуют несколько причин, почему использование IronPython может быть нецелесообразным для некоторых проектов:
-
Ограниченная поддержка Python 3:
IronPython в основном поддерживает Python 2, а поддержка Python 3 всё ещё находится в стадии разработки. Это ограничение делает IronPython менее привлекательным, так как большинство современных проектов используют Python 3.
-
Ограниченная поддержка библиотек:
IronPython не поддерживает многие популярные библиотеки Python, особенно те, которые написаны на C или используют C-расширения, такие как NumPy или pandas. Это значительно ограничивает функциональность и возможности для анализа данных и научных вычислений.
-
Меньшее сообщество и документация:
Сообщество IronPython меньше по сравнению с основной реализацией Python (CPython), что приводит к ограниченной документации и поддержке. Это может усложнить решение проблем и получение помощи.
Python.NET
Python.NET (или Python for .NET) — это проект, который позволяет использовать интерпретатор CPython внутри приложений .NET. Он предоставляет возможность взаимодействия между C# и Python-кодом. Несмотря на его мощные возможности, есть несколько причин, почему его использование может быть нецелесообразным:
-
Сложность настройки:
Настройка Python.NET может быть сложной и требует определённых усилий для корректной интеграции Python и .NET кода. Это может включать проблемы с совместимостью и конфигурацией среды выполнения.
-
Перформанс и накладные расходы:
Использование Python.NET может приводить к дополнительным накладным расходам и проблемам с производительностью, так как интерпретатор Python должен быть встроен и взаимодействовать с .NET средой. Это может быть неэффективно для приложений, требующих высокой производительности.
-
Стабильность и поддержка:
Хотя Python.NET является мощным инструментом, его развитие и поддержка также могут быть ограничены по сравнению с основной реализацией Python и инструментами .NET. Это может привести к нестабильности и сложности в решении возникающих проблем.
Альтернатива: Вызов внешнего процесса
Вместо использования IronPython или Python.NET, вызов внешнего процесса с использованием стандартного интерпретатора Python (например, через ProcessStartInfo
в C#) предлагает следующие преимущества:
-
Полная поддержка Python 3 и всех библиотек:
Вы можете использовать все возможности и библиотеки CPython, включая популярные пакеты для анализа данных и машинного обучения, такие как pandas, NumPy, TensorFlow и другие.
-
Простота настройки и использования:
Вызов внешнего процесса прост в настройке и не требует сложной интеграции. Вы можете легко запустить скрипт Python и обработать его вывод.
-
Независимость среды:
Этот подход позволяет изолировать среду выполнения Python от .NET приложения, что упрощает управление зависимостями и обновлениями.
Необходимые условия
Установлен .NET SDK
Установлен Python
Базовое понимание C# и Python
Шаг 1: Настройка окружения Python
-
Создайте виртуальное окружение: Откройте терминал или командную строку и перейдите в ваш проект.
python -m venv myenv
-
Активируйте виртуальное окружение:
-
На Windows:
myenv\Scripts\activate
-
На Unix или MacOS:
source myenv/bin/activate
-
-
Установите необходимые пакеты Python:
pip install pandas
Шаг 2: Создание скрипта Python
Создайте скрипт Python, который выполняет простую задачу с использованием pandas
. Сохраните этот скрипт как main.py
в директории PythonApplication
.
import sys
import pandas as pd
def add(a, b):
return a + b
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python main.py <num1> <num2>")
sys.exit(1)
# Создание DataFrame с входными числами
data = {'num1': [sys.argv[1]], 'num2': [sys.argv[2]]}
df = pd.DataFrame(data)
# Преобразование колонок в тип float
df = df.astype(float)
# Выполнение сложения
result = add(df['num1'].iloc[0], df['num2'].iloc[0])
print(result)
Шаг 3: Создание приложения на C#
-
Создайте новое консольное приложение .NET:
dotnet new console -n CSharpPython cd CSharpPython
Измените
Program.cs
для выполнения скрипта Python:
Замените содержимоеProgram.cs
следующим кодом:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
try
{
// Получение относительного пути к скрипту Python
string pythonScriptPath = FindPythonScript("PythonApplication", "main.py");
// Проверка наличия скрипта Python
if (string.IsNullOrEmpty(pythonScriptPath) || !File.Exists(pythonScriptPath))
{
Console.WriteLine($"Ошибка: скрипт Python не найден по пути: {pythonScriptPath}");
return;
}
// Путь к интерпретатору Python в виртуальном окружении
string pythonInterpreterPath = FindPythonInterpreter("PythonApplication", "myenv", "Scripts", "python.exe");
// Проверка наличия интерпретатора Python
if (string.IsNullOrEmpty(pythonInterpreterPath) || !File.Exists(pythonInterpreterPath))
{
Console.WriteLine($"Ошибка: интерпретатор Python не найден по пути: {pythonInterpreterPath}");
return;
}
// Числа для сложения
double num1 = 5.5;
double num2 = 4.5;
// Преобразование чисел в строки с использованием invariant culture (точка как десятичный разделитель)
string num1Str = num1.ToString(CultureInfo.InvariantCulture);
string num2Str = num2.ToString(CultureInfo.InvariantCulture);
// Начало измерения времени выполнения
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// Настройка процесса для вызова скрипта Python
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = pythonInterpreterPath;
start.Arguments = $"{pythonScriptPath} {num1Str} {num2Str}";
start.WorkingDirectory = Path.GetDirectoryName(pythonScriptPath);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true; // Захват стандартного вывода ошибок
start.CreateNoWindow = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.WriteLine($"Результат: {result}");
}
using (StreamReader errorReader = process.StandardError)
{
string error = errorReader.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
Console.WriteLine($"Ошибка: {error}");
}
}
process.WaitForExit();
int exitCode = process.ExitCode;
Console.WriteLine($"Процесс завершился с кодом: {exitCode}");
}
// Остановка измерения времени выполнения
stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;
Console.WriteLine($"Время выполнения: {ts.TotalMilliseconds} мс");
}
catch (Exception ex)
{
Console.WriteLine($"Исключение: {ex.Message}");
Console.WriteLine($"Стек вызовов: {ex.StackTrace}");
}
}
static string FindPythonScript(string directory, string scriptName)
{
return FindFile(directory, scriptName);
}
static string FindPythonInterpreter(string directory, string venvDirectory, string scriptsDirectory, string pythonExe)
{
return FindFile(directory, Path.Combine(venvDirectory, scriptsDirectory, pythonExe));
}
static string FindFile(string directory, string fileName)
{
// Получение начального каталога сборки
string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
while (currentDirectory != null)
{
string foundPath = SearchDirectory(currentDirectory, directory, fileName);
if (foundPath != null)
{
return foundPath;
}
// Переход в родительский каталог
currentDirectory = Directory.GetParent(currentDirectory)?.FullName;
}
// Если файл не найден, вернуть null
return null;
}
static string SearchDirectory(string baseDirectory, string targetDirectory, string fileName)
{
// Получение полного пути к целевой директории
string targetPath = Path.Combine(baseDirectory, targetDirectory);
if (Directory.Exists(targetPath))
{
string filePath = Path.Combine(targetPath, fileName);
if (File.Exists(filePath))
{
return filePath;
}
// Рекурсивный поиск в подкаталогах
foreach (string subDirectory in Directory.GetDirectories(targetPath))
{
string foundPath = SearchDirectory(subDirectory, string.Empty, fileName);
if (foundPath != null)
{
return foundPath;
}
}
}
return null;
}
}
Шаг 4: Запуск приложения
-
Соберите приложение на C#:
dotnet build
-
Запустите приложение:
dotnet run
Заключение
Следуя этим шагам, вы создадите приложение на C#, которое интегрируется со скриптом Python, используя виртуальное окружение для управления зависимостями Python. Этот подход гарантирует, что все необходимые библиотеки Python будут доступны, и позволяет надежно выполнять скрипт Python. Кроме того, вы реализовали измерение времени выполнения, чтобы отслеживать производительность скрипта Python.
Комментарии (15)
Fox_exe
25.05.2024 10:01А зачем вообще вызывать из интерпретируемого кода другой интерпретируемый код? В чём выгода? Почему, к примеру, нельзя вызвать из собранного C# приложения внешний скрипт написанный на C#?
roboter
25.05.2024 10:01+1OldNileCrocodile
25.05.2024 10:01В том числе и у Java есть JavaCompiler. Только там кодогенерация идëт через процессор Аннотаций. Lombok, например.
jeeper
25.05.2024 10:01Не, все такие штуки проваливаются и не имеют продолжения. А если это не местечковый проект, то его потом ещё и выкорчевать придется и тратить на это время. К тому же это не модная тенденция. Это не по молодёжному. Сейчас модно в микросервисы и для задачи похожей на вашу этого использовать apache thrift.
OldNileCrocodile
25.05.2024 10:01Лучше бы написали как через Python создавать билды . NET приложений. Выполняешь скрипт, получаешь исходники и скрипт оболочки cmd для выполнения сборки (А ля клон make). Или пойти дальше и запилить клон gradle.
nav68
25.05.2024 10:01Компилятор для Питона уже давно просится. Интерпретатор удобнее для разработки. А для пользователя лучше иметь скомпилированный старт.ехе
sashamsuper
25.05.2024 10:01Pandas нет, но есть Microsoft.Data.Analysis, в принципе можно найти аналог
savostin
Вы не "скрипт подключили", а "выполнили стороннее приложение". И Python тут вообще ни при чем.
ShadowGreg Автор
Ага всё верно. Была задача именно такой. Ну питон конечно же причём. Если он не установлена на машине и не подтянуты зависимости библиотек - то скрипт не работает.