Pull to refresh

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

Reading time6 min
Views26K

Введение

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

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments12

Articles