Pythonnet. Как запустить C# код из Python

—

от автора

Введение

На сегодняшний день Python является одним из самых популярных языков программирования, но даже это не помогает ему покрыть все потребности программистов. Самый очевидный минус чистого CPython — это его скорость, поэтому некоторые программисты выбирают для своих задач другие языки программирования, а кто-то просто реализует узкие места на C/C++ и подключает их к Python.

Однако бывают случаи, когда есть некая база кода, написанного на C#, а возможности быстро переписать всё на Python/C/C++ нет. Тогда встает вопрос “как подключить C# к Python?”. Для этого была разработана библиотека pythonnet. В этой статье разберем: как запустить C# код из Python и что из этого может получиться.

Реализация

Для сравнения скорости выполнения C# и Python я буду ссылаться на одну из прошлых статей.

Библиотека pythonnet работает с .dll файлами, поэтому весь код необходимо будет преобразовывать в динамически подключаемые библиотеки. Чтобы создать .dll файл из C# необходимо установить visual studio и при создании проекта указать, что проект будет создан для библиотеки классов (я дал название проекту: “MyTestCS”, в будущем dll файл будет носить такое же название как и проект):

В качестве примера будем использовать магазин из прошлой статьи, который оптимизировали силами самого питона и других языков. Создадим структуру для одного товара на C#:

public struct DataGoods     {         public string name;         public int price;         public string unit;         public DataGoods(string name, int price, string unit)         {             this.name = name;             this.price = price;             this.unit = unit;         }     }

Теперь реализуем класс самого магазина. В нем создадим метод для заполнения магазина товарами:

public class ShopClass     {         public string name;         public List<DataGoods> listGoods;         public ShopClass(string name)         {             this.name = name; 						this.listGoods = new List<DataGoods>();         }         /// <summary>         /// Метод для создания товаров в магазине         /// </summary>         /// <param name="numberGoods"> Количество объектов в магазине </param>         public void createShopClass(int numberGoods) {             List<DataGoods> lGoods = new List<DataGoods>();             for (int i = 0; i < numberGoods; i++) {                 lGoods.Add(new DataGoods("телефон", 20000, "RUB"));                 lGoods.Add(new DataGoods("телевизор", 45000, "RUB"));                 lGoods.Add(new DataGoods("тостер", 2000, "RUB"));             }             this.listGoods = lGoods;         }    }

После того, как класс был создан, приступим к подключению C# кода к Python проекту. Сначала создадим .dll файл из C# проекта (достаточно нажать команду ctrl+shift+B). В папке bin->debug->netstandart2.0 проекта (путь зависит от того, какие конфигурации среды стоят у вас) появится файл с названием проекта и расширением .dll (именно этот файл будет подключаться к программе на Python).

Далее разберемся с проектом на Python. Необходимо установить библиотеку pythonnet, выполнив команду:

pip install pythonnet

В проекте создадим файл main.py, а также поместим библиотеку “MyTestCS.dll” в папку с проектом:

Теперь можно подключать библиотеку в main.py, для этого сначала импортируем clr (clr позволяет рассматривать пространства имен CLR как пакеты Python):

import clr

Укажем путь до нашего .dll файла:

pathDLL = os.getcwd() + "\\MyTestCS.dll"

Чтобы подгрузить нужную нам библиотеку необходимо прописать следующий код:

clr.AddReference(pathDLL)

После чего можно импортировать модуль и всё, что в нем содержится. Если напрямую сделать импорт MyTestCS:

import MyTestCS print(MyTestCS) >>> <module 'MyTestCS'>

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

Создадим экземпляр класса ShopClass и DataGoods через Python и обратимся к полям этих классов.

from MyTestCS import ShopClass, DataGoods shop = ShopClass("Тест магазин") shop.createShopClass(1)  goods = DataGoods("чехол для телефона", 500, "RUB")  print(shop.name) >>> Тест магазин print(shop.listGoods) >>> [<MyTestCS.DataGoods object at 0x000001D04C3FE3C8>, <MyTestCS.DataGoods object at 0x000001D04C3FE438>, <MyTestCS.DataGoods object at 0x000001D04C3FE400>] print(shop.listGoods[1].name, shop.listGoods[1].price, shop.listGoods[1].unit) >>> телевизор 45000 RUB print(goods.name, goods.price, goods.unit) >>> чехол для телефона 500 RUB

Как итог, получилось вызвать код C# из Python и поработать с классами. Теперь протестируем производительность создания 200*100000 товаров через метод createShopClass:

shop = ShopClass("Тест магазин") s = time.time() shop.createShopClass(200 * 100000) print("СОЗДАНИЕ ТОВАРОВ НА C#:", time.time() - s) >>> СОЗДАНИЕ ТОВАРОВ НА C#: 2.9043374061584473

В прошлой статье время создания такого количества товаров заняло примерно 44 секунды. Использование C# вместо Python позволило ускорить этот процесс примерно в 15 раз, что является очень хорошим результатом.

Проблемы

Однако не может же быть всё настолько хорошо, чтобы броситься переписывать куски кода Python на C#. И это так. Попробуем из Python вручную дополнить товарами магазин:

shop = ShopClass("Тест магазин 1") s = time.time() shop.createShopClass(500000) print("СОЗДАЛИ ТОВАРЫ ЧЕРЕЗ C#:", time.time()-s) >>> СОЗДАЛИ ТОВАРЫ ЧЕРЕЗ C#: 0.07325911521911621  shop = ShopClass("Тест магазин 2") s = time.time() for _ in range(500000):         goods1 = DataGoods("телефон", 20000, "RUB")         goods2 = DataGoods("телевизор", 45000, "RUB")         goods3 = DataGoods("тостер", 2000, "RUB")         shop.listGoods.extend([goods1, goods2, goods3]) print("СОЗДАЛИ ТОВАРЫ ЧЕРЕЗ PYTHON:", time.time()-s) >>> СОЗДАЛИ ТОВАРЫ ЧЕРЕЗ PYTHON: 5.2899720668792725

И проверим аналогичный код, написанный на Python:

istGoods = [] class DataGoods2:         def __init__(self, name, price, unit):             self.name = name             self.price = price             self.unit = unit  s = time.time() for _ in range(500000):         goods1 = DataGoods2("телефон", 20000, "RUB")         goods2 = DataGoods2("телевизор", 45000, "RUB")         goods3 = DataGoods2("тостер", 2000, "RUB")         listGoods.extend([goods1, goods2, goods3]) print("СОЗДАЛИ PYTHON ОБЪЕКТЫ:", time.time()-s) >>> СОЗДАЛИ PYTHON ОБЪЕКТЫ: 1.2972710132598877

Код чистого питона работает быстрее, чем дополнение объекта, созданного из модуля C#. Это связано с тем, что доступ к объектам, написанным на C#, занимает довольно много времени. Чтобы избежать таких проблем, необходимо писать всю логику работы с классом внутри C# кода, и не выносить эту логику в Python. Изменение скорости выполнения кода будет заметно при подсчете суммы всех товаров. Реализуем функцию подсчета суммы товаров на C# (внутри класса ShopClass):

public long getSumGoods() {     long sumGoods = 0;     foreach (DataGoods goods in this.listGoods) {       	sumGoods += goods.price;     }     return sumGoods; }

А также на Python:

shop = ShopClass("Магазин 3") shop.createShopClass(1000000) s = time.time() shop.getSumGoods() print("ВРЕМЯ НА СУММУ ТОВАРОВ C#:", time.time()-s) >>> ВРЕМЯ НА СУММУ ТОВАРОВ C#: 0.0419771671295166 sumGoods = 0 for goods in shop.listGoods:      sumGoods += goods.price print("ВРЕМЯ НА СУММУ ТОВАРОВ PYTHON:", time.time()-s) >>> ВРЕМЯ НА СУММУ ТОВАРОВ PYTHON: 6.205681085586548

Python код выполняется гораздо медленнее, чем внутренние методы C#.

Многопоточность

Так как в C# отсутствует GIL, то мне стало интересно протестировать работу многопоточности в C# и попробовать запустить потоки в C# через Python. Для начала протестируем протестируем создание 3х классов ShopClass последовательно и заполним их 3.000.000 товаров:

public class testShop     {         public void testSpeedNoThread(int count)         {             testShopClass(count);             testShopClass(count);             testShopClass(count);         }         public static void testShopClass(int count)         {             ShopClass shop = new ShopClass("Магазин");             shop.createShopClass(count);         } }

Python код для запуска:

tshop = testShop() s = time.time() tshop.testSpeedNoThread(3000000) print("СОЗДАЕМ ПОСЛЕДОВАТЕЛЬНО 3 МАГАЗИНА:", time.time()-s) >>> СОЗДАЕМ ПОСЛЕДОВАТЕЛЬНО 3 МАГАЗИНА: 2.1849117279052734

Дополним класс testShop для работы с потоками новым методом:

public static void testThread() {     ExThread obj = new ExThread();     Thread thr = new Thread(new ThreadStart(obj.mythread1));     Thread thr2 = new Thread(new ThreadStart(obj.mythread1));     Thread thr3 = new Thread(new ThreadStart(obj.mythread1));     thr.Start();     thr2.Start();     thr3.Start();     thr.Join();     thr2.Join();     thr3.Join(); }

И создадим новый вспомогательный класс:

public class ExThread {    public void mythread1()      {          ShopClass shop = new ShopClass("Магазин");          shop.createShopClass(3000000);      } }

Запустим Python код для проверки работы потоков:

s = time.time() tshopThread = testShop() tshopThread.testThread() print("СОЗДАЕМ 3 ПОТОКА C# ДЛЯ 3х МАГАЗИНОВ:", time.time()-s) >>> СОЗДАЕМ 3 ПОТОКА C# ДЛЯ 3х МАГАЗИНОВ: 0.6765928268432617

Вывод

Использование частей кода, написанных на C# в Python возможно, но при таком подходе есть и свои минусы, например, скорость доступа к объектам. Использование pythonnet целесообразно, если имеются какие-то части кода, которые нет возможности переписать на Python, но они требуют подключения к основному проекту на Python.

P.S. есть и другие способы ускорить python, например, написать библиотеку на C/C++ или переписать часть кода на Cython с меньшими проблемами. В данной статье лишь представлена возможность использования C# и Python вместе. Также существует реализация Python для платформы Microsoft.NET под названием IronPython.

ссылка на оригинал статьи https://habr.com/ru/post/556230/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *