На сегодняшний день существует несколько тысяч языков программирования, каждый из которых создавался с определенной целью, пытаясь изменить и улучшить недостатки своих предшественников. Так, например, появился язык Kotlin, который был нацелен на замену Java в мобильной разработке. В 2010 году увидел свет язык Rust, разработчики которого пытались создать быстрый и безопасный язык, который закрывал бы многие недостатки C/C++.
Сейчас практически никто не ставит цели создать универсальный язык для всех задач и всех платформ, так как в каждой области есть свои потребности и нюансы для языка. Например, если в системной разработке требуется следить за памятью, то в местах, где нужно написать простой рабочий продукт, можно пренебречь тем, сколько памяти использует язык для своей работы.
Но что делать, если необходимо использовать несколько языков программирования в одном проекте?
Цель
Зачастую бывает так, что один язык не очень хорошо может справляться с теми задачами, которые нужно решить. Для этого программист может без проблем пересесть на другой язык. Но что делать, если уже имеется какая-то часть кода, которая написана на одном языке программирования, а другая часть кода на другом? Например, есть приложение, написанное на Python и есть какие-то структуры, модули или методы, которые написаны на Java (C/C#/JS) и уже оптимизированы с учетом этого языка, а переписывание этого кода на Python может занять много времени, да и код на Python будет выполняться намного медленнее и использовать больше памяти.
Можно попробовать объединить все эти наработки в одно приложение. Благо на сегодняшний день уже реализовано много библиотек, которые позволят без лишних проблем это сделать.
Цель статьи: попробовать написать одно приложение, где будет использоваться код, написанный на 5 разных языках программирования.
В качестве примера языки будут реализовать следующее: Cи будет проверять число на простоту методом квадратного корня, C# проверит число на простоту методом Милера-Рабина, Java проверит число на простоту методом Ферма, Python будет раскладывать число на множители, а JS будет высчитывать сумму числового ряда для полученных множителей.
P.s. примеры собраны очень примитивные, так как цель проекта - показать, как можно объединить несколько кусков кода вместе.
Java
Для того, чтобы запустить код Java из Python необходимо создать maven java проект (я пользуюсь IntellIJ). В нем создать модуль (я назвал его pkg_java) и в нем создать класс (название: JavaPrime) с логикой проверки числа на простоту методом Ферма:
package pkg_java;
import java.util.*;
public class JavaPrime {
public static void main(String[] args) {
boolean rez = is_prime_ferma(3574);
System.out.println(rez);
}
public static boolean is_prime_ferma(int number){
List<Integer> rnd_list = new ArrayList<Integer>();
int rnd_value;
boolean is_prime = true;
while (rnd_list.size() < 20){
rnd_value = get_rnd_value(number+1, number+100000);
if ((number % rnd_value != 0) && !rnd_list.contains(rnd_value)){
rnd_list.add(rnd_value);
}
}
for (int rnd_number : rnd_list) {
if (mod_pow(rnd_number, number-1, number) != 1){
is_prime = false;
break;
}
}
return is_prime;
}
public static int get_rnd_value(int min, int max){
return (int)Math.floor(Math.random()*(max-min+1)+min);
}
public static long mod_pow(long a, long b, int m) {
a %= m;
if (b == 0) return 1;
else if (b % 2 == 0) {
return mod_pow((a * a) % m, b / 2, m);
}
else return (a * mod_pow(a, b - 1, m)) % m;
}
}
Далее необходимо создать .jar файл из данного модуля, для этого в File->Project Structure необходимо создать новый Jar артефакт:
После чего выполнить Build->Build Artifacts, высветится список всех доступных артефактов, необходимо выбрать только что созданный и нажать build, в итоге будет создан .jar файл модуля.
Теперь необходимо подключить .jar файл к Python. Для этого первым делом нужно установить библиотеку JPype1, выполнив pip install jpype1
, и подключить созданный .jar к проекту:
from jpype import *
jarpath = "java_is_prime.jar"
startJVM(getDefaultJVMPath(), "-ea", "-Djava.class.path=%s" % (jarpath))
pkgJava = JPackage("pkg_java")
java_prime_class = pkgJava.JavaPrime()
print("JAVA CLASS LOADED")
print("TEST JAVA:", java_prime_class.is_prime_ferma(12))
>>> JAVA CLASS LOADED
>>> TEST JAVA: False
Модуль Java был успешно загружен, теперь можно пользоваться тестом Ферма.
C#
Для того, чтоб запустить C# код в Python, нужно для начала создать библиотеку классов C# (я использовал VS2019):
Назовем проект is_prime_csharp (данный проект в будущем будет импортироваться в Python с таким же названием). Реализуем логику алгоритма Милера-Рабин:
using System;
using System.Numerics;
namespace is_prime_csharp
{
public class miler_rabin_csharp
{
public static bool test_miler_rabin(int n)
{
if (n == 2 || n == 3) {
return true;
}
if (n < 2 || n % 2 == 0) {
return false;
}
// we represent n - 1 in the form (2 ^ s) t, where t is odd, this can be done by sequentially dividing n - 1 by 2
int t = n - 1;
int s = 0;
while (t % 2 == 0) {
t = t / 2;
s++;
}
Random rnd = new Random();
// let's take 8 rounds to determine the prime of a number.
for (int i= 0; i < 8; i++) {
int a = rnd.Next(2, n-2);
// x ← a ^ t mod n, we calculate using the exponentiation modulo
int x = (int)BigInteger.ModPow(a, t, n);
if (x == 1 || x == n - 1) {
continue;
}
for (int j = 0; j < s - 1; j++) {
// x ← x^2 mod n
x = (int)BigInteger.ModPow(x, 2, n);
// if x == 1 then return "compound"
if (x == 1) {
return false;
}
// if x == n - 1, then go to the next iteration of the outer loop
if (x == n - 1) {
break;
}
}
if (x != n - 1) {
return false;
}
}
// return "probably simple"
return true;
}
}
}
Далее достаточно нажать ctrl+shift+B, чтобы скомпилировать .dll файл C# модуля. Данную .dll необходимо поместить в проект с Python.
Теперь необходимо установить библиотеку pythonnet, выполнив команду pip install pythonnet
. Данная библиотека позволяет рассматривать пространство имен clr как модули в python. И через python можно загрузить модуль C#:
import clr
path_с_sharp = os.getcwd() + "\\is_prime_csharp.dll"
clr.AddReference(path_с_sharp)
from is_prime_csharp import miler_rabin_csharp
print("C# CLASS LOADED")
print("TEST:", miler_rabin_csharp.test_miler_rabin(12))
>>> C# CLASS LOADED
>>> TEST: False
Теперь модуль C# готов к работе, методом Милера-Рабина для проверки числа на простоту можно пользоваться.
Си
Для связи С с Python сначала реализуем алгоритм квадратного корня для проверки числа на простоту:
#include <stdbool.h>
#include <math.h>
bool is_prime_sqrt(int number){
bool prime = true;
if (number == 1 || (number%2 == 0)){
prime = false;
}
else{
for (int i=2; i<=sqrt(number);i++){
if (number%i == 0){
prime = false;
break;
}
}
}
return prime;
}
Теперь создадим .dll из сишного кода. Для этого в папке с файлом is_prime_c.c через командную строку выполним:
gcc -c -DBUILD_DLL is_prime_c.c
gcc -shared -o is_prime_c.dll is_prime_c.o -Wl,--add-stdcall-alias
После чего в папке появится файл .dll, который так же необходимо поместить в Python проект и подключить:
from ctypes import *
c_prime = WinDLL("./is_prime_c.dll")
print("C MODULE LOADED")
print("TEST C:", bool(c_prime.is_prime_sqrt(12)))
>>> C MODULE LOADED
>>> TEST C: False
Модуль C успешно загружен.
JavaScript
Связь Python и JS будет идти через библиотеку EEL, для этого сначала установим её в Python, выполнив pip install eel
. Далее создадим HTML документ и JS файл, в HTML файле добавим eel.js и js файл с логикой суммы ряда (см подробнее проект на github). В js файле реализуем логику суммы ряда и обернем функцию дополнительно в eel.expose для того, чтобы эту функцию можно было вызвать из Python:
eel.expose(solve_example);
function solve_example(list_of_numbers, x) {
let sum = 0;
let part = 0;
for (let i in list_of_numbers) {
part = list_of_numbers[i] * ((-1)**(i%3)) * (x**((-1)**i))
sum += part;
}
return (sum).toFixed(3);
}
В main.py пропишем логику запуска программы:
from logic import *
import eel
eel.init("front")
eel.start("index.html", size=(600, 489), port=51534)
С такой структурой программы:
И вызовем метод JS из Python:
print(eel.solve_example([2, 2, 3], 12)())
>>> 59.833
Python
Для начала реализуем метод факторизации чисел:
def factorization(number):
parts = []
delim = 2
while delim**2 <= number:
if number % delim == 0:
parts.append(delim)
number //= delim
else:
delim += 1
if number > 1:
parts.append(number)
return parts
В файле logic.py соберем все проверки в одной функции , чтобы данную функцию можно было вызвать из JS обернем её в eel.expose:
@eel.expose
def start_proc_number_py(number):
part_answer = {}
part_answer["python"] = factorization(number)
part_answer["c"] = bool(c_prime.is_prime_sqrt(number))
part_answer["java"] = java_prime_class.is_prime_ferma(number)
part_answer["c#"] = miler_rabin_csharp.test_miler_rabin(number)
part_answer["js"] = eel.solve_example(part_answer["python"], number)()
rezult = {}
rezult["python"] = part_answer["python"]
rezult["c"] = "Простое" if part_answer["c"] else "Составное"
rezult["java"] = "Простое" if part_answer["java"] else "Составное"
rezult["c#"] = "Простое" if part_answer["c#"] else "Составное"
rezult["js"] = part_answer["js"]
return rezult
Результат
Программа имеет простой графический интерфейс:
Необходимо ввести число в поле “Число” и нажать кнопку “выполнить”. После чего через JS обработать нажатие данной кнопки и вызвать метод из Python:
document.querySelector("#start-programm").onclick = async (e) => {
let number = parseInt(document.querySelector("#number-js").value);
let result = await eel.start_proc_number_py(number)();
show_result(result);
}
function show_result(dict_result){
document.querySelector("#result-java").value = dict_result["java"];
document.querySelector("#result-c").value = dict_result["c"];
document.querySelector("#result-csharp").value = dict_result["c#"];
document.querySelector("#result-js").value = dict_result["js"];
document.querySelector("#result-python").value = dict_result["python"].join(', ');
}
Теперь можно запустить программу и проверить, будет ли всё вместе работать:
Вывод
Связать несколько языков программирования вместе в одной программе возможно, но это не совсем хорошая идея, так как при запуске программы на стороннем ПК надо быть уверенным, что у пользователя установлены нужные сервисы/зависимости/ПО, например, стоит ли JVM. Для быстрой проверки работоспособности каких-то идей, модулей, логики можно попробовать использовать подход, описанный в статье. Данный способ позволяет экономить кучу времени, вместо того, чтобы мучаться и переписывать код на другой язык в надежде, что всё будет работать как надо. Этот способ может подойти в тех случаях, когда нет возможности разработать адекватную микросервисную архитектуру приложения, а нужно использовать несколько разных кусков кода/модулей.
Как итог, получилось связать Python + JS + Java + C + C# (+ HTML + CSS) в одной программе, сделав при этом полноценное десктопное приложение, которое работает быстро без лишних задержек при обращении к методам, написанным на другом языке. В таком подходе есть плюсы: можно использовать фишки других языков (например, использовать преимущества скорости в C, Java, C# с (или без) использованием многопоточности, задействующей несколько ядер процессора, а также можно реализовывать структуры, которые будут использовать меньше памяти нежели Python).
Комментарии (10)
xakep666
06.09.2021 09:19+1Упомяну и про Go. Существует 2 способа вызвать его функции из Python: cffi (тогда в go надо экспортировать функции, есть ограничения по типам) и подключение как расширения (extension). Последний способ сложнее в реализации (надо определённым образом готовить и собирать библиотеку на go), но ограничений по типам почти нет.
daniilgorbenko Автор
06.09.2021 09:28Про Go думал, но в первую подборку языков он не попал. Думаю, поискать еще решений на других языках и сделать вторую часть с другими языками, чтобы у сообщества была информация в прямом доступе о том, какие языки можно вызывать из Python.
questor
06.09.2021 10:43+1Цель статьи: попробовать написать одно приложение, где будет использоваться код, написанный на 5 разных языках программирования.
Интересно, а если составить матрицу и для каждого из этих ЯП посмотреть сколько других ЯП он может подключить -- какие будут максимумы и минимумы?
daniilgorbenko Автор
06.09.2021 11:31Мне кажется, что там получилась бы довольно интересная матрица. Правда, хуже дела обстоят с менее популярными/молодыми языками, так как для них меньше готовых инструментов существует.
domix32
06.09.2021 13:33+3не хватает хаба "ненормальное программирование".
А вообще забавы ради можно сделать какой-нибудь dll quine где каждый из языков ссылается на каждый из языков и зовет его из третьего языка.
vlakir
Познавательно, спасибо. До кучи еще про F2Py можно упомянуть. В научных кругах весьма популярен.
daniilgorbenko Автор
Спасибо! Про F2Py не знал. Сейчас почитал про него, действительно можно было бы дополнить им.