PyO3 – 如何将枚举返回给 Python 模块?

huangapple go评论88阅读模式
英文:

PyO3 - How to return enums to python module?

问题

I'm trying to build a Python package from Rust using PyO3. Right now I'm stuck trying to return enums Rust type to Python.

我正在尝试使用PyO3从Rust构建Python包。目前,我在尝试将Rust中的enums类型返回给Python时遇到了困难。

I have a simple enum like so:

pub enum Lang {
	Deu,
	Eng,
	Fra
}

我有一个简单的枚举如下:

pub enum Lang {
	Deu,
	Eng,
	Fra
}

And in lib.rs

#[pyfunction]
fn detect_language(text: &str) -> PyResult<????> {
   // Do some stuff ....
   res: Lang = Do_some_stuff(text)
   Ok(res)
}

#[pymodule]
fn pymylib(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(detect_language, m)?)?;
    Ok(())
}

lib.rs中:

#[pyfunction]
fn detect_language(text: &str) -> PyResult<????> {
   // 做一些操作 ....
   res: Lang = Do_some_stuff(text)
   Ok(res)
}

#[pymodule]
fn pymylib(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(detect_language, m)?)?;
    Ok(())
}

In Python code

from pymylib import detect_language

res = detect_language('Ceci est un test')
print(res) # Lang:Fra ???

在Python代码中:

from pymylib import detect_language

res = detect_language('Ceci est un test')
print(res) # Lang:Fra ???
英文:

I'm trying to build a Python package from Rust using PyO3. Right now I'm stuck trying to return enums Rust type to Python.

I have a simple enum like so:

pub enum Lang {
	Deu,
	Eng,
	Fra
}

And in lib.rs

#[pyfunction]
fn detect_language(text: &amp;str) -&gt; PyResult&lt;????&gt; {
   // Do some stuff ....
   res:Lang = Do_some_stuff(text)
   Ok(res)
}

#[pymodule]
fn pymylib(_py: Python, m: &amp;PyModule) -&gt; PyResult&lt;()&gt; {
    m.add_function(wrap_pyfunction!(detect_language, m)?)?;
    Ok(())
}

In Python code

from pymylib import detect_language

res=detect_language(&#39;Ceci est un test&#39;)
print(res) # Lang:Fra ???

答案1

得分: 2

一种方法是使用 #[pyclass] 属性来创建 Python 类,将其与 Rust 枚举关联起来。还要确保从 Rust 代码中导出此类,以便在 Python 层进行比较。
例如,

use pyo3::prelude::*;

#[pyclass]
pub enum Lang {
    Deu,
    Eng,
    Fra
}

#[pyfunction]
fn detect_language(text: &str) -> PyResult<Lang> {
    // 在这里编写您的实际代码
    // 但为了测试目的,让我们返回 `Deu` 变量。
    Ok(Lang::Deu)
}

/// 一个在 Rust 中实现的 Python 模块。
#[pymodule]
fn pymylib(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(detect_language, m)?)?;
    m.add_class::<Lang>()?;
    Ok(())
}

现在在 Python 解释器中,您可以执行以下操作,

>>> from pymylib import Lang
>>> import pymylib
>>> 
>>> pymylib.detect_language("1")
Lang.Deu
>>> pymylib.detect_language("1") == Lang.Deu
True

参见

英文:

One approach would to use #[pyclass] attribute to make the Python class from Rust Enum. Also make sure to export this class from Rust code, so that you can do the comparison on python layer.
ie,

use pyo3::prelude::*;

#[pyclass]
pub enum Lang {
    Deu,
    Eng,
    Fra
}

#[pyfunction]
fn detect_language(text: &amp;str) -&gt; PyResult&lt;Lang&gt; {
    // Write your actual code here
    // But for testing purpose let&#39;s return `Deu` varient.
    Ok(Lang::Deu)
}

/// A Python module implemented in Rust.
#[pymodule]
fn pymylib(_py: Python, m: &amp;PyModule) -&gt; PyResult&lt;()&gt; {
    m.add_function(wrap_pyfunction!(detect_language, m)?)?;
    m.add_class::&lt;Lang&gt;()?;
    Ok(())
}

Now in Python interpreter you can do,

&gt;&gt;&gt; from pymylib import Lang
&gt;&gt;&gt; import pymylib
&gt;&gt;&gt; 
&gt;&gt;&gt; pymylib.detect_language(&quot;1&quot;)
Lang.Deu
&gt;&gt;&gt; pymylib.detect_language(&quot;1&quot;) == Lang.Deu
True

See also

答案2

得分: 1

与Python的枚举变量命名指南不同,我通常会将Rust枚举转换为PyO3接口中的字符串,然后在Python端将其包装在StrEnum中。这不需要太多额外的代码:

// interface.rs

#[pyfunction]
fn detect_language(text: &str) -> PyResult<&'static str> {
    Ok(match actual_detect_language(text) {
        Lang::Deu => "deu",
        Lang::Eng => "eng",
        Lang::Fra => "fra",
    })
}
# interface.py
from enum import StrEnum

import pymylib

class Language(StrEnum):
    DEU = "deu"
    ENG = "eng"
    FRA = "fra"

def detect_language(text: str) -> Language:
    return Language(pymylib.detect_language(text))

如果需要在Rust端将枚举还原回来,也可以执行相同的操作:StrEnum继承自str,因此可以直接传递给任何需要str的PyO3函数。在Rust端的匹配中,您可以对"other"变体使用panic,因为使用StrEnum可以确保变体存在。

英文:

In combination with the fact that python's naming guidelines for enum variants are different to rust's, I generally turn the rust enum into a string in the pyo3 interface, and then wrap it in a StrEnum on the python side. This doesn't require too much extra code:

// interface.rs

#[pyfunction]
fn detect_language(text: &amp;str) -&gt; PyResult&lt;&amp;&#39;static str&gt; {
    Ok(match actual_detect_language(text) {
        Lang::Deu =&gt; &quot;deu&quot;,
        Lang::Eng =&gt; &quot;eng&quot;,
        Lang::Fra =&gt; &quot;fra&quot;,
    })
}
# interface.py
from enum import StrEnum

import pymylib

class Languge(StrEnum):
    DEU = &quot;deu&quot;
    ENG = &quot;eng&quot;
    FRA = &quot;fra&quot;

def detect_language(text: str) -&gt; Language:
    return Language(pymylib.detect_language(text))

You can do the same thing in reverse if you need the enum back on the rust side: StrEnum inherits from str and so can be passed straight into any pyo3 function which takes a str. You can just panic on the "other" variant in the match on the rust side because using the StrEnum guarantees the variant exists.

huangapple
  • 本文由 发表于 2023年5月24日 17:40:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322128.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定