Домашнее задание#
Требования к оформлению отчета
Все отчеты оформляются шрифтом Times New Roman, размер шрифта для основного тектса 14,
для названия глав и пунктов - 16. Межстрочный интервал у текста - 1.5пт.
Все рисунки подписываются по центру снизу в формате Рис. Номер рисунка. Название Рисунка
Все таблицы подписываются сверху справа в формате Таблица. Номер таблицы. Название таблицы.
Все вставки с кодом подписываются сверху справа  в формате Листинг. Номер листинга. Название листинга.
Задание
Название работы: "Разработка Модуля Python на С++"
- По вариантам реализовать модуль на С++ и произвести подключение в Python проект (дать формулировку задания, номер варианта, привести какой метод компиляции был выбран (скрины консольных команд или настройки проекта))
 - Произвести замеры по скорости работы модуля по времени! (измерения времени производить внутри файла C++)
 - Реализовать аналогичную функцию на чистом Python
 - Произвести замеры по скорости работы чисто питоновской функции
 - Замеры ввести в таблицу. Округление времени до 2 знаков после запятой.
 
| № Теста | Количество итераций | C++ (с) | Python (с) | 
|---|---|---|---|
| 1 | 10000 | 5.23 | 12.45 | 
| 2 | 50000 | 18.67 | 45.92 | 
| 3 | 100000 | 36.89 | 90.75 | 
- Построить график по вашим данным, пользуясь любым удобным способом (рекомендуется Excel)
 - Сделать вывод во сколько раз и кто быстрее
 - Приложить в виде Листинга 1 и Листинга 2 коды программ
 - Дать ссылку на гитхаб
 
Задание повышенной сложности.
Методичские указания#
CTypes + Visual studio CODE#
В Windows библиотека динамической компоновки (DLL) является исполняемым файлом, который выступает в качестве общей библиотеки функций и ресурсов. Динамическая компоновка — это возможность операционной системы. Она позволяет исполняемому файлу вызывать функции или использовать ресурсы, хранящиеся в отдельном файле. Эти функции и ресурсы можно компилировать и развертывать отдельно от использующих их исполняемых файлов.
Библиотека DLL не является отдельным исполняемым файлом. Библиотеки DLL выполняются в контексте приложений, которые их вызывают. Операционная система загружает библиотеку DLL в область памяти приложения. Это делается либо при загрузке приложения (неявная компоновка), либо по запросу во время выполнения (явная компоновка). Библиотеки DLL также упрощают совместное использование функций и ресурсов различными исполняемыми файлами. Несколько приложений могут осуществлять одновременный доступ к содержимому одной копии библиотеки DLL в памяти.
Создайте новый проект в Visual studio CODE

Из списка вариантов выберите пункт Библиотека динамической компоновки DLL. Выберите место для проекта. 

Автоматически сгененрируются несколько файлов pch.h и dllmain
Question
// dllmain.cpp : Определяет точку входа для приложения DLL.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
                    DWORD  ul_reason_for_call,
                    LPVOID lpReserved
                    )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Для дополнения кода необходимо добавить в проект (создать) файл хедера (например, Header.h) и соотвествующий ему файл cpp (например, Source.cpp)
Рассмотрим следующуюзадачу: пусть мне надо создать библиотеку из 3 функций (Функция для сложения целых чисел, Функция для сложения чисел с плавающей точкой, Функция вычисления синуса для N случайных чисел)
Приведем пример Header.h для данной задачи:
#pragma once
#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)  // Для компиляции библиотеки
#else
#define MYLIBRARY_API __declspec(dllimport) // Для использования библиотеки
#endif
extern "C" {
    MYLIBRARY_API int add_int(int num1, int num2);   // Функция для сложения целых чисел
    MYLIBRARY_API float add_float(float num1, float num2);  // Функция для сложения чисел с плавающей точкой
    MYLIBRARY_API float compute_sin(int N);  // Функция вычисления синуса для N случайных чисел
}
MYLIBRARY_API — это макрос, который используется для управления экспортом и импортом функций из DLL. Когда библиотека компилируется, этот макрос подставляет правильную директиву компилятора:-
__declspec(dllexport) используется для указания, что символ (функция или переменная) должен быть экспортирован из DLL.-
__declspec(dllimport) используется для указания, что символ будет импортирован из DLL.
Приведем пример соотвествующего cpp кода, в котором не будет ничего интересного, за исключением необходимости включения в код #include "pch.h"
#include "pch.h"
#include "Header.h"
#include <cmath>
#include <cstdlib>
#include <ctime>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
int add_int(int num1, int num2) {
    return num1 + num2;
}
float add_float(float num1, float num2) {
    return num1 + num2;
};
float compute_sin(int N) {
    clock_t start_time = clock(); // Начало замера времени
    float result = 0.0f;
    for (int i = 0; i < N; ++i) {
        float angle = static_cast<float>(rand()) / RAND_MAX * 2 * M_PI; // Случайный угол в радианах
        result += sin(angle); // Вычисление синуса
    }
    clock_t end_time = clock(); // Конец замера времени
    float elapsed_time = float(end_time - start_time) / CLOCKS_PER_SEC; // Время в секундах
    return elapsed_time; // Возвращаем время выполнения
}
После добавления файлов можно собрать решение

Затем найдите dll файл (мой проект назывался Dll1, поэтому название файла Dll1.dll)
Создайте проект на python (например в Visual studio Code)
Скопируйте *.dll файл в дирректорию, где будем писать python код

В Python-коде мы сначала импортируем модуль ctypes . Затем функция CDLL из того же модуля используется для загрузки C-библиотеки. Теперь функции из C-кода доступны для нас через переменную adder . Когда мы вызываем adder.add_int() , то автоматически вызывается C-функция add_int.
from ctypes import *
# Загружаем библиотеку
adder = CDLL('./adder.dll')
# Находим сумму целых чисел
res_int = adder.add_int(4,5)
print("Сумма 4 и 5 = " + str(res_int))
# Находим сумму действительных чисел
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print("Сумма 5.5 и 4.1 = " + str(add_float(a, b)))
Интерфейс модуля ctypes позволяет использовать питоновские целые числа и строки при вызове C-функций.
Для других типов, например логического или действительных чисел, мы должны использовать корректные ctypes .
Python/C API#
C/Python API это, вероятно, наиболее широко применяемый метод - не благодаря своей простоте, а потому что он позволяет оперировать Python объектами из C кода.
Этот метод подразумевает написание C-кода специально для работы с Python. Все объекты Python представляются как структуры PyObject и заголовочный файл Python.h предоставляет различные функции для работы с объектами.
Например, если PyObject одновременно PyListType (список), то мы можем использовать функцию PyList_Size() , чтобы получить длину списка. 
Это эквивалентно коду len(some_list) в Python. Большинство основных функций/операторов для стандартных Python объектов доступны в C через Python.h .
Для работы вам может понадобиться Техническая документация Python/C API
Давайте напишем С-библиотеку для суммирования всех элементов списка Python (все элементы являются числами).
Создайте новый проект в Visual studio CODE

Создайте консольное приложение.
Найдите где располагается Python. 
Настройте среду VS code(вкладка проект->свойства) согласно таблице:
| Вкладка и раздел | Свойство | Значение | 
|---|---|---|
| Общие свойства > конфигурации | Имя цели | Укажите имя модуля, который будет ссылаться на него из Python в from...import инструкциях, таких как суперфасткод. Это же имя будет использоваться в C++ при определении модуля для Python. Чтобы применить имя проекта в качестве имени модуля, сохраните значение по умолчанию $<(ProjectName)>. Для python_d.exe добавьте _d в конец файла. | 
| Тип конфигурации | Динамическая библиотека (.dll) | |
| C/C++ > Общие | Дополнительные каталоги включаемых файлов | Добавьте папку "Включить Python" в соответствии с вашей установкой (например, c:\Python310\include). | 
| C/C++ > Препроцессор | Определения препроцессора | Если он присутствует, измените значение _DEBUG на NDEBUG, чтобы оно соответствовало версии CPython. При использовании python_d.exe оставьте это значение неизменным. | 
| C/C++ > Создание кода | Библиотека времени выполнения | Многопоточные библиотеки DLL (/MD) для соответствия версии выпуска (nondebug) CPython. При использовании python_d.exe оставьте это значение как многопоточная библиотека DLL отладки (/MDd). | 
| Базовые проверки среды выполнения | По умолчанию | |
| Компоновщик > Общие | Дополнительные каталоги библиотек | Добавьте подходящую для вашей установки папку Python libs с файлами .lib (например, c:\Python310\libs). Убедитесь, что указали папку libs, содержащую файлы .lib, но не папку Lib, содержащую файлы .py. | 
Пример настройки C/C++ > Общие| Дополнительные каталоги включаемых файлов

Пример настройки  Компоновщик > Общие  | Дополнительные каталоги библиотек   

Tip
Не перепутайте директорию libs и Lib!
Напишем код в файле Sourse.cpp
#include <Python.h>
// Эта функция вызывается из Python кода
static PyObject* addList_add(PyObject* self, PyObject* args) {
    PyObject* listObj;
    // Входящие аргументы находятся в кортеже
    // В нашем случае есть только один аргумент - список, на который мы будем
    // ссылаться как listObj
    if (!PyArg_ParseTuple(args, "O", &listObj))
        return NULL;
    // Длина списка
    long length = PyList_Size(listObj);
    // Проходимся по всем элементам
    int i, sum = 0;
    for (i = 0; i < length; i++) {
        // Получаем элемент из списка - он также Python-объект
        PyObject* temp = PyList_GetItem(listObj, i);
        // Мы знаем, что элемент это целое число - приводим его к типу C long
        long elem = PyLong_AsLong(temp);  // Используем PyLong_AsLong вместо PyInt_AsLong
        sum += elem;
    }
    // Возвращаемое в Python-код значение также Python-объект
    // Приводим C long к Python integer
    return Py_BuildValue("i", sum);
}
// Немного документации для 'add'
static char addList_docs[] =
"add( ): add all elements of the list\n";
/*
Эта таблица содержит необходимую информацию о функциях модуля
<имя функции в модуле Python>, <фактическая функция>,
<ожидаемые типы аргументов функции>, <документация функции>
*/
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
    {NULL, NULL, 0, NULL}
};
/*
addList имя модуля и это блок его инициализации.
<желаемое имя модуля>, <таблица информации>, <документация модуля>
*/
static struct PyModuleDef addList_module = {
    PyModuleDef_HEAD_INIT,
    "addList",   // Имя модуля
    "Add all the lists", // Документация модуля
    -1,           // Размер состояния модуля
    addList_funcs // Таблица функций
};
PyMODINIT_FUNC PyInit_addList(void) {  // Для Python 3 используем PyInit_<module>
    return PyModule_Create(&addList_module); // Используем PyModule_Create вместо Py_InitModule3
}
Tip
Комментарии к коду
- Заголовочный файл 
представления типов объектов в Python) и определения функций (для работы с
Python-объектами).
- Дальше мы пишем функцию, которую собираемся вызывать из Python. По
соглашению, имя функции принимается {module-name}_{function-name}, которое в
нашем случае - addList_add . Подробнее об этой функции будет дальше.
- Затем заполняем таблицу, которая содержит всю необходимую информацию о
функциях, которые мы хотим иметь в модуле. Каждая строка относится к функции,
последняя - контрольное значение (строка из null элементов).
- Затем идёт блок инициализации модуля - PyMODINIT_FUNC init{module-name} .
Функция addList_add принимает аргументы типа PyObject (args также является
кортежем, но поскольку в Python всё является объектами, мы используем
унифицированный тип PyObject ). Мы парсим входные аргументы (фактически,
разбиваем кортеж на отдельные элементы) при помощи PyArg_ParseTuple() . Первый
параметр является аргументом для парсинга. Второй аргумент - строка,
регламентирующая процесс парсинга элементов кортежа args. Знак на N-ой позиции
строки сообщает нам тип N-ого элемента кортежа args, например - 'i' значит integer, 's' -
строка и 'O' - Python-объект. Затем следует несколько аргументов, где мы хотели бы
хранить выходные элементы PyArg_ParseTuple() . Число этих аргументов равно числу
аргументов, которые планируется передавать в функцию модуля и их позиционность
должна соблюдаться
Например, если мы ожидаем строку, целое число и список в
таком порядке, сигнатура функции будет следующего вида:
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);
В данном случае, нам нужно извлечь только объект списка и сохранить его в
переменной listObj . Затем мы используем функцию PyList_Size() чтобы получить
длину списка. Логика совпадает с len(some_list) в Python.
Теперь мы итерируем по списку, получая элементы при помощи функции
PyLint_GetItem(list, index) . Так мы получаем PyObject*. Однако, поскольку мы знаем,
что Python-объекты еще и PyIntType , то используем функцию PyInt_AsLong(PyObj *)
для получения значения. Выполняем процедуру для каждого элемента и получаем
сумму.
Затем переместим наш файл .cpp в директорию, где будет находится файл python.
Создаем файл setup.py
Tip
setup.py — это скрипт, используемый в Python для создания и установки пакетов и модулей. Он описывает, как собрать и  установить пакеты Python, а также конфигурацию, необходимую для компиляции расширений на C или C++
Пример кода setup.py для Source.cpp ( Имя пакета млэете использовать любое, которое вам захочется импортировать в последствии, в моем случае addList)
from distutils.core import setup, Extension 
setup(
    name='addList',               # Имя пакета
    version='1.0',                 # Версия пакета
    ext_modules=[Extension('addList', ['Source.cpp'])]  # Указание расширения на C/C++, которое будет собрано из файла Source.cpp
)
Теперь необхоимо открыть консоль и прописать команду (пути к файлу не учитываются, считается, что у вас открыта текущая дирректория проекта)
    python setup.py build_ext --inplace
build_ext: Этот параметр указывает на сборку расширений, то есть компиляцию C/C++ кода, указанных в файле setup.py, в динамическую библиотеку (например, .pyd или .so).--inplace: Этот параметр заставляет установку происходить непосредственно в текущую директорию, а не в стандартную директорию установки для Python. --force: Пересобирает расширение, даже если оно уже было собрано ранее. 
Если команда не работает, убедитесь, что у вас установлен следующий пакет:
    pip install setuptools
Теперь проект собран, осталось импортировать модуль cpp, как обычную библиотеку в py
    import addList
    l = [1,2,3,4,5, 9]
    print("Сумма элементов списка - " + str(l) + " = " +  str(addList.add(l)))  
MinGW (для тех, у кого не получается)#
Чтобы проверить есть ли MinGW сделайте команду в CMD:
    g++ --help
Пример, когда он не установлен:

Установочный файл GCC 11.4.0 + MinGW-w64 11.0.0 (UCRT) release 1 можно найти на этой странице.
Вы выбираете версию Win32 и Win64 в зависимости от своей системы.

Вы скачаете архив winlibs-x86_64-posix-seh-gcc-11.4.0-mingw-w64ucrt-11.0.0-r1.7z

Данные этого архива вытаскиваем. Внутри единственная папка minGW64
Её необходимо переместить в корень диска С (C:/minGW64)

Откройте меню “Система” в “Панели управления”:

Из меню “Система” перейдите в “Дополнительные параметры системы”:

Выберите “Переменные среды”:

Выберите переменную Path и нажмите кнопку “Изменить...“

Добавьте в новую строку полный путь до директории mingw32\bin и нажмите кнопку OK.

Чтобы проверить, что настройка выполнена успешно, откройте консоль (не в директории mingw32\bin) и выполните команду g++ --help:.

Теперь скомпилируем C-файл в .dll -файл (.so под Linux). Так мы получим файл
adder.dll.
Скомпелируем файл на C++ в динамическую библиотеку (.dll) в операционной системе Windows, воспользовавшись MinGW. Для этого в консоли нужно прописать команду:
g++ -shared -o ИМЯ_ВАШЕГО_ФАЙЛА.dll МЯ_ВАШЕГО_ФАЙЛА.cpp
Пример кода для компиляции adder.сpp
// Простой C-файл - суммируем целые и действительные числа
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2){
return num1 + num2;
}
float add_float(float num1, float num2){
return num1 + num2;
}
После копмиляции C-файл в .dll -файл (.so под Linux). Так мы получим файл adder.dll.
Теперь мы можем легко подключить наш файл и вызвать функцию (при условии, что мы поместили его в файлы проекта!!!)

from ctypes import *
# Загружаем библиотеку
adder = CDLL('./adder.dll')
# Находим сумму целых чисел
res_int = adder.add_int(4,5)
print("Сумма 4 и 5 = " + str(res_int))
# Находим сумму действительных чисел
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print("Сумма 5.5 и 4.1 = " + str(add_float(a, b)))
В примере выше, C-файл содержит простой код - две функции: одна для нахождения
суммы двух целых чисел, другая - действительных.
В Python-коде мы сначала импортируем модуль ctypes . Затем функция CDLL из того
же модуля используется для загрузки C-библиотеки. Теперь функции из C-кода
доступны для нас через переменную adder . Когда мы вызываем adder.add_int() , то
автоматически вызывается C-функция add_int . Интерфейс модуля ctypes позволяет
использовать питоновские целые числа и строки при вызове C-функций.
Для других типов, например логического или действительных чисел, мы должны
использовать корректные ctypes .
Мы делаем это при передаче параметров в
adder.add_float() . Сначала мы создаём требуемый тип c_float из float , затем
используем в качестве аргумента для C-кода. Этот метод простой и аккуратный, но
ограниченный. Мы, к примеру, не можем оперировать объектами на стороне C-кода.
Пример замера времени#
Вариант начала файла Сpp:
#include <Python.h>
#include <iostream>
#include <cmath>
#include <ctime>
// Функция для вычисления синуса N раз и измерения времени выполнения
double calculateSinusNTimes(int N) {
    clock_t start_time = clock(); // Запуск таймера
    // Вычисление синуса N раз
    for (int i = 0; i < N; ++i) {
        double result = std::sin(0.5); // Вычисление синуса числа
        // Здесь можно использовать другие значения для вычисления синуса
    }
    clock_t end_time = clock(); // Завершение таймера
    double time_taken = double(end_time - start_time) / CLOCKS_PER_SEC; // Вычисление времени выполнения в секундах
    return time_taken;
Вариант функции для Python
import time
import math
def calculate_sin_n_times(N):
    start_time = time.time()  # Запуск таймера
    # Вычисление синуса N раз
    for i in range(N):
        result = math.sin(0.5)  # Вычисление синуса числа
        # Здесь можно использовать другие значения для вычисления синуса
    end_time = time.time()  # Завершение таймера
    time_taken = end_time - start_time  # Вычисление времени выполнения в секундах
    return time_taken
Варианты#
| Вариант | Метод | Задача | 
|---|---|---|
| 1 | CTypes | Сложение двух массивов | 
| 2 | Python/C API | Вычитание двух массивов | 
| 3 | CTypes | Умножение вектора на число | 
| 4 | Python/C API | Деление вектора на число | 
| 5 | CTypes | Поиск максимума в массиве | 
| 6 | Python/C API | Поиск минимума в массиве | 
| 7 | CTypes | Скалярное произведение двух векторов | 
| 8 | Python/C API | Вычисление среднего арифметического по массиву | 
| 9 | CTypes | Вычисление медианного значения массива | 
| 10 | Python/C API | Вычисление математического ожидания | 
| 11 | CTypes | Вычисление стандартного отклонения | 
| 12 | Python/C API | Вычисление суммы элементов массива | 
| 13 | CTypes | Реализовать функцию Distinct по заданному массиву, возвращающую список только различных элементов массива | 
| 14 | Python/C API | Умножение вектора на число | 
| 15 | CTypes | Вычисление среднего арифметического по массиву | 
| 16 | CTypes | Вычисление математического ожидания | 
| 17 | Python/C API | Вычисление медианного значения массива | 
| 18 | CTypes | Вычисление суммы элементов массива | 
| 19 | Python/C API | Вычисление стандартного отклонения | 
| 20 | CTypes | Деление вектора на число | 
| 21 | Python/C API | Вычисление длины вектора | 
| 22 | CTypes | Создать нулевой массив заданного размера | 
| 23 | Python/C API | Вычисление расстояния между векторами | 
| 24 | CTypes | Вычисление длины вектора | 
| 25 | Python/C API | Создать нулевой массив заданного размера | 
| 26 | CTypes | Вычисление расстояния между векторами | 
| 27 | Python/C API | Создать единичный массив заданного размера | 
| 28 | CTypes | Подсчет количества четных значений в массиве | 
| 29 | Python/C API | Реализовать функцию Distinct, по заданному массиву, возвращающую список только различных элементов массива | 
| 30 | CTypes | Подсчет количества совпадающих элементов между двумя массивами |