본문 바로가기
프로그래밍/러스트(rust)

Windows 11, pyo3 와 maturin 을 활용해 러스트에서 파이썬 코드 실행하기

by K-인사이터 2024. 11. 17.
반응형

안녕하세요 
K-인사이트입니다. 

 

근황: 러스트와 윈도우 운영체제 

최근 러스트와 윈도우 운영체제에 빠져서 헤어나오지 못하는 중입니다. 이직 혹은 전배를 준비중 입니다. 

오늘은 Windows 11 환경에서 pyo3 와 maturin 을 활용한 rust 에서 파이썬 스크립트 실행하기에 대해 핵심을 설명드리겠습니다. 

 

이 내용은 클로드나 퍼플렉서티에서도 제대로 답변을 하지 못하는 내용으로 더욱 글의 가치가 높다고 할 수 있겠습니다. 

만약 AI 검색에 제 글이 노출된다면 열심히 블로그 글을 작성한 보람이 없을 수도 있겠습니다만... ㅎㅎㅎ 저작권 문제가 언젠가 해결되길 바라겠습니다. (보고있나 퍼플렉서티??)

 

 

파이썬과 러스트를 통합하기 위한 pyo3 와 maturin

파이썬의 유연함을 러스트로 가져오려고 한다면 간단한 검색을 통해 pyo3, maturin 이 필요하다는 것을 쉽게 알 수 있습니다. 먼저, Rust 코드를 Python에서 사용할 수 있게 하는 기본적인 방법을 알아보겠습니다. 이를 위해 두 가지를 설치해야 합니다. PyO3는 Maturin을 사용하여 생성된 프로젝트의 의존성에 포함되며, Virtualenv는 Maturin이 작동하는 데 필요합니다.

  • maturin
  • virutalenv
cargo install --locked maturin
pip install virtualenv

 

 

스킬 1. 러스트 코드를 파이썬에서 사용하도록 하기

maturin을 사용하여 프로젝트를 생성합니다. 그 후 파이썬 가상환경을 만들고, 러스트 코드를 파이썬에서 사용할 수 있도록 빌드하겠습니다.

maturin new pyrusty
✔ 🤷 Which kind of bindings to use?
  📖 Documentation: <https://maturin.rs/bindings.html> · pyo3
  ✨ Done! New project created pyrusty

 

 

처음에 프로젝트가 생성되면 아래와 같이 기본적인 파이썬 함수와 모듈에 대한 정의가 포함됩니다.

즉, import pyrusty; pyrusty.sum_as_string(100, 200); 과 같은 파이썬 코드가 실행되는 것을 기대하는 코드입니다.

use pyo3::prelude::*;

/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// A Python module implemented in Rust.
#[pymodule]
fn pyrusty(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

 

다음으로 .venv 파일을 생성합니다.

python -m venv .venv 

 

이제, 러스트 코드를 빌드하고 maturin 으로 develop 명령을 실행하여 가상환경에 파이썬모듈을 설치합니다.

cargo build
maturin develop

 

 

 

스킬 2. 파이썬 스크립트를 러스트 코드에서 실행하기 

그렇다면 단순히 러스트 코드에서 파이썬 프로그램을 실행시키는 것은 안될까요? 물론 가능합니다. pyo3 버전에 따라 코드가 다르기 때문에 maturin 에서 제공하는 안정적인 버전을 중심으로 pyo3 버전을 0.22.4 버전으로 맞추어줍니다.

# Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2021"

[dependencies]
pyo3 = { version = "0.22.4", features = [ "auto-initialize"] }


파이썬 코드를 스레드 내에서 실행 시키거나 혹은 스레드 외부에서 실행시키는 예시이다. 0.22.4 버전과 0.23.0 버전의 코드를 조금 다르므로 주의합니다. https://pyo3.rs/v0.22.4/python-from-rust/calling-existing-code 링크의 코드를 참고합니다.

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {
    std::thread::spawn(|| {
        let result: Result<f64, PyErr> = Python::with_gil(|py| {
            let activators = PyModule::from_code_bound(
                py,
                r#"
import math
import sys
# import pyrusty
def relu(x):
    print(sys.path)
    return max(0.0, x)
                "#,
                "activators.py", 
                "activators"
            )?;

            let result: f64 = activators.getattr("relu")?.call1((-1.0,))?.extract()?;
            Ok(result)
        });
        // println!("스레드 내부 결과: {:?}", result);
        println!("스레드 내부 결과: {:?}", result.unwrap());
    });

    Python::with_gil(|py| {
        let activators = PyModule::from_code_bound(
            py,
            r#"
def relu(x):
    """see https://en.wikipedia.org/wiki/Rectifier_(neural_networks)"""
    return max(0.0, x)

def leaky_relu(x, slope=0.01):
    return x if x >= 0 else x * slope
        "#,
            "activators.py",
            "activators",
        )?;
    
        let relu_result: f64 = activators.getattr("relu")?.call1((-1.0,))?.extract()?;
        assert_eq!(relu_result, 0.0);
    
        let kwargs = [("slope", 0.2)].into_py_dict_bound(py);
        let lrelu_result: f64 = activators
            .getattr("leaky_relu")?
            .call((-1.0,), Some(&kwargs))?
            .extract()?;
        assert_eq!(lrelu_result, -0.2);
        println!("스레드 외부 결과: {:?}", lrelu_result);
        Ok(())
    })
}

 

스킬 3. 파이썬 코드 파일들을 러스트 코드에서 실행하기 

[시나리오]

응용을 확장해보면, pyrust 패키지가 설치된 컨테이너를 실행하고, 여기서 서버 버전의 vscode를 구동하는 것을 생각할 수 있습니다. 또는 Microsoft의 브랜딩을 제거한 vscode의 서버 버전을 구동하는 것도 가능합니다. 모듈이나 파이프라인 개발자는 제공된 템플릿을 사용하여 파이썬으로 코드를 작성하고 테스트합니다. 테스트 데이터를 입력하는 등의 작업을 수행합니다. 코드 개발이 완료되면 업로드하고 서버나 컨테이너에서 파이썬 코드를 러스트 내에서 실행합니다.

 

위와 같은 시나리오를 구현하려면 필연적으로 약간의 약속과 함께 러스트가 여러 파이썬 소스코드 파일들을 인식해서 실행해야 합니다. 결과부터 말하자면 pyo3 는 이러한 기능을 충분히 제공하고 있으며 아래와 같이 코드와 프로젝트 구조를 생성합니다. 

 

즉, 아래와 같은 폴더의 구조를 가지고 있다고 가정해 보겠습니다. python_app 은 시나리오에서 러스트 코드에서 실행하길 바라는 파이썬 소스코드들입니다. src 폴더에는 러스트 코드가 위치하게 됩니다. 

C:.
|   .gitignore
|   Cargo.toml
|   
+---python_app
|   |   app.py
|   |   
|   \\---utils
|           foo.py
|
\\---src
        lib.rs
        main.rs

 

 

main.rs 파일의 소스코드는 아래와 같습니다. Path:new 를 원하는 경로로 변경하면 잘 실행됨을 확인할 수 있습니다. 

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use pyo3::types::PyList;
use std::fs;
use std::path::Path;

fn main() {
	 let path = Path::new("C:\\여러분의 경로로 변경\\python_app");
    let py_app = fs::read_to_string(path.join("app.py"))?;
    let from_python = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
        let syspath = py
            .import_bound("sys")?
            .getattr("path")?
            .downcast_into::<PyList>()?;
        syspath.insert(0, &path)?;
        let app: Py<PyAny> = PyModule::from_code_bound(py, &py_app, "", "")?
            .getattr("run")?
            .into();
        app.call0(py)
    });

    println!("py: {}", from_python?);
    Ok(())
}

 

아래 링크는 pyo3 v0.22.4 의 내용으로 파이썬 파일들을 경로로 접근하여 실행하는 방법을 다루고 있는 내용입니다. 참고바랍니다. 

https://pyo3.rs/v0.22.4/python-from-rust/calling-existing-code#include-multiple-python-files

 

Executing existing Python code - PyO3 user guide

If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation: PyModule::import_bound can be used to get handle to a Python module from Rust. You can

pyo3.rs

 

 

맺음말

지금까지 윈도우 환경에서 pyo3 와 maturin 을 활용해 러스트에서 파이썬 코드 실행하는 방법을 다루었습니다. 파이썬의 유연함을 러스트의 문맥에서 실행한다는 것은 견고하고 안전한 백엔드를 구성하는 기초 능력이 될 것입니다. 

 

이 글이 여러분의 커리어에 도움이 되길 바랍니다. 

 

 

이상입니다.

K-인사이트 올림. 

 

반응형