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