Введение
На сегодняшний день 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.
