Введение

Интеграция скриптов Python в приложение на C# позволяет использовать обширные библиотеки Python, сохраняя при этом все преимущества строго типизированного языка C#. Это особенно полезно в ситуациях, когда необходимо выполнять сложные вычисления или анализ данных, для которых уже существуют мощные и хорошо зарекомендовавшие себя библиотеки Python, такие как pandas или NumPy. Вместо того чтобы разрабатывать новые API или сервисы для выполнения этих задач, можно непосредственно вызывать скрипты Python из C# кода, что упрощает процесс разработки и уменьшает время на интеграцию.

Этот гайд предоставляет пошаговое руководство по созданию приложения на C#, которое вызывает скрипт Python, использует виртуальное окружение для управления зависимостями Python и измеряет время выполнения скрипта. Такой подход позволяет комбинировать лучшие стороны обоих языков, обеспечивая эффективность и гибкость в разработке приложений.

Почему не IronPython и не Python.NET?

IronPython

IronPython — это реализация Python для .NET, которая позволяет выполнять код Python в среде .NET. Тем не менее, существуют несколько причин, почему использование IronPython может быть нецелесообразным для некоторых проектов:

  1. Ограниченная поддержка Python 3:

    • IronPython в основном поддерживает Python 2, а поддержка Python 3 всё ещё находится в стадии разработки. Это ограничение делает IronPython менее привлекательным, так как большинство современных проектов используют Python 3.

  2. Ограниченная поддержка библиотек:

    • IronPython не поддерживает многие популярные библиотеки Python, особенно те, которые написаны на C или используют C-расширения, такие как NumPy или pandas. Это значительно ограничивает функциональность и возможности для анализа данных и научных вычислений.

  3. Меньшее сообщество и документация:

    • Сообщество IronPython меньше по сравнению с основной реализацией Python (CPython), что приводит к ограниченной документации и поддержке. Это может усложнить решение проблем и получение помощи.

Python.NET

Python.NET (или Python for .NET) — это проект, который позволяет использовать интерпретатор CPython внутри приложений .NET. Он предоставляет возможность взаимодействия между C# и Python-кодом. Несмотря на его мощные возможности, есть несколько причин, почему его использование может быть нецелесообразным:

  1. Сложность настройки:

    • Настройка Python.NET может быть сложной и требует определённых усилий для корректной интеграции Python и .NET кода. Это может включать проблемы с совместимостью и конфигурацией среды выполнения.

  2. Перформанс и накладные расходы:

    • Использование Python.NET может приводить к дополнительным накладным расходам и проблемам с производительностью, так как интерпретатор Python должен быть встроен и взаимодействовать с .NET средой. Это может быть неэффективно для приложений, требующих высокой производительности.

  3. Стабильность и поддержка:

    • Хотя Python.NET является мощным инструментом, его развитие и поддержка также могут быть ограничены по сравнению с основной реализацией Python и инструментами .NET. Это может привести к нестабильности и сложности в решении возникающих проблем.

Альтернатива: Вызов внешнего процесса

Вместо использования IronPython или Python.NET, вызов внешнего процесса с использованием стандартного интерпретатора Python (например, через ProcessStartInfo в C#) предлагает следующие преимущества:

  1. Полная поддержка Python 3 и всех библиотек:

    • Вы можете использовать все возможности и библиотеки CPython, включая популярные пакеты для анализа данных и машинного обучения, такие как pandas, NumPy, TensorFlow и другие.

  2. Простота настройки и использования:

    • Вызов внешнего процесса прост в настройке и не требует сложной интеграции. Вы можете легко запустить скрипт Python и обработать его вывод.

  3. Независимость среды:

    • Этот подход позволяет изолировать среду выполнения Python от .NET приложения, что упрощает управление зависимостями и обновлениями.

Необходимые условия

  • Установлен .NET SDK

  • Установлен Python

  • Базовое понимание C# и Python

Шаг 1: Настройка окружения Python

  1. Создайте виртуальное окружение: Откройте терминал или командную строку и перейдите в ваш проект.

    python -m venv myenv
  2. Активируйте виртуальное окружение:

    • На Windows:

      myenv\Scripts\activate
    • На Unix или MacOS:

      source myenv/bin/activate
  3. Установите необходимые пакеты 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#

  1. Создайте новое консольное приложение .NET:

    dotnet new console -n CSharpPython
    cd CSharpPython
  2. Измените 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: Запуск приложения

  1. Соберите приложение на C#:

    dotnet build
  2. Запустите приложение:

    dotnet run

Заключение

Следуя этим шагам, вы создадите приложение на C#, которое интегрируется со скриптом Python, используя виртуальное окружение для управления зависимостями Python. Этот подход гарантирует, что все необходимые библиотеки Python будут доступны, и позволяет надежно выполнять скрипт Python. Кроме того, вы реализовали измерение времени выполнения, чтобы отслеживать производительность скрипта Python.

Комментарии (15)


  1. savostin
    25.05.2024 10:01
    +10

    Вы не "скрипт подключили", а "выполнили стороннее приложение". И Python тут вообще ни при чем.


    1. ShadowGreg Автор
      25.05.2024 10:01

      Ага всё верно. Была задача именно такой. Ну питон конечно же причём. Если он не установлена на машине и не подтянуты зависимости библиотек - то скрипт не работает.


  1. DieSlogan
    25.05.2024 10:01
    +4

    Хотя бы упомяните о IronPython или хотя бы Python.NET


    1. ryanl
      25.05.2024 10:01

      Да, тоже на языке крутилось. Запуск внешнего процесса конечно можно назвать интеграцией, однако подход, как говорится "в лоб", часто является самым неэффективным из всех возможных.


      1. JerryI
        25.05.2024 10:01

        Лучший вариант будет вероятно shared memory ну или хотя бы сокеты / трубы


  1. Fox_exe
    25.05.2024 10:01

    А зачем вообще вызывать из интерпретируемого кода другой интерпретируемый код? В чём выгода? Почему, к примеру, нельзя вызвать из собранного C# приложения внешний скрипт написанный на C#?


  1. roboter
    25.05.2024 10:01
    +1

    На C# можно динамически компилировать C#
    На C# можно динамически компилировать C#


    1. OldNileCrocodile
      25.05.2024 10:01

      В том числе и у Java есть JavaCompiler. Только там кодогенерация идëт через процессор Аннотаций. Lombok, например.


  1. Ydav359
    25.05.2024 10:01
    +6

    Как много текста про обычное использование Process.Start


  1. jeeper
    25.05.2024 10:01

    Не, все такие штуки проваливаются и не имеют продолжения. А если это не местечковый проект, то его потом ещё и выкорчевать придется и тратить на это время. К тому же это не модная тенденция. Это не по молодёжному. Сейчас модно в микросервисы и для задачи похожей на вашу этого использовать apache thrift.


    1. AlexZyl
      25.05.2024 10:01

      @moderator
      похоже консерву вскрыли


  1. Slparma
    25.05.2024 10:01

    Какая чушь, просто жесть, целая статья о запуске стороннего приложения


  1. OldNileCrocodile
    25.05.2024 10:01

    Лучше бы написали как через Python создавать билды . NET приложений. Выполняешь скрипт, получаешь исходники и скрипт оболочки cmd для выполнения сборки (А ля клон make). Или пойти дальше и запилить клон gradle.


  1. nav68
    25.05.2024 10:01

    Компилятор для Питона уже давно просится. Интерпретатор удобнее для разработки. А для пользователя лучше иметь скомпилированный старт.ехе


  1. sashamsuper
    25.05.2024 10:01

    Pandas нет, но есть Microsoft.Data.Analysis, в принципе можно найти аналог