匹配多种可能的类型?

Matching multiple possible types?

由于我强大的弱类型编程背景,我对 Rust 非常非常陌生并且正在努力使用它。

下面的代码应该将通过 PYO3 从 Python 接收到的数据写入 XLSX 工作表。我只是不知道如何处理最后一个匹配项,因为“value”是 PyAny 类型的(也就是说,它的方法 extract 可以输出多种类型,例如 String、f32 等,我想要一个特定的行为取决于提取的类型)。

也许我可以为每个潜在的提取类型链接匹配(如果第一个输出错误,请尝试下一个),但我怀疑可能有更好的方法。也许我只是在用错误的设计来解决问题。欢迎任何见解。

pub trait WriteValue {
    fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError>;
}

impl WriteValue for String {
    fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
        worksheet.write_string(row, col, &self, format)
    }
}

impl WriteValue for f32 {
    fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
        worksheet.write_number(row, col, f64::from(*self), format)
    }
}

fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
    let workbook = Workbook::new(path);
    let mut worksheet = workbook.add_worksheet(None)?;

    let format_bold = workbook.add_format().set_bold();

    for (row_index, values) in data {

        let mut col_idx: u16 = 0;

        for value in values {
            col_idx += 1;
            let row_format= match &row_index {
                0 => Some(&format_bold),
                _ => None
                };
            match value.extract::<String>() {
                Ok(x) => x.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?,
                Err(_) => { }
                }
        }
    }
    workbook.close()
    }

PyAny可以试试downcast to any other Python type。我不精通 PyO3,但我在这里看到的唯一方法是尝试向下转换为您支持的类型,否则可能会引发错误:

fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
    let workbook = Workbook::new(path);
    let mut worksheet = workbook.add_worksheet(None)?;

    let format_bold = workbook.add_format().set_bold();

    for (row_index, values) in data {

        let mut col_idx: u16 = 0;

        for value in values {
            col_idx += 1;
            let row_format= match &row_index {
                0 => Some(&format_bold),
                _ => None
            };
            if let Ok(string) = value.downcast::<PyString> {
                // handle pystring object
                string.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?;
                ...
            } else if let Ok(int) = value.downcast::<PyInt> {
                // handle pyint object
                ...
            } else {
                // error, or not supported
            }
        }
    }
    workbook.close()
}

这主要是一个 pyo3 API 问题,我不认为 pyo3 有 built-in“multiextract”,虽然我不是很熟悉它,所以它可能。

但是,首先,由于您不关心 Err 子句,您可以通过简单地链接 if let 语句来简化代码,它们是语法糖,但对于一元或二元布尔条件,它们真的很方便 e.g.

if let Ok(x) = value.extract::<String>() {
    x.write_value(...)
} else if let Ok(x) = value.extract::<f32>() {
    // handle this case and possibly add a bunch more
} else {
    // handle no case matching (optional if should be ignored)
}

其次,看起来 pyo3 可以让你 derive enums,因为 WriteValue 显然是一个内部特征,所以派生相应的枚举是有意义的:

#[derive(FromPyObject)]
enum Writables {
    #[pyo3(transparent, annotation = "str")]
    String(String),
    #[pyo3(transparent, annotation = "float")]
    Float(f32),
    // put the other cases here
}

然后你可以extractthat并一次匹配所有变体(并单独处理“不支持的类型”)。

事实上,此时 trait 可能是不必要的,除非它被用于其他东西,你可以直接在枚举上使用你的 write_value 方法。

side-note:将 python 浮点数(它是双精度数)提取到 f32 然后立即将其扩大到 f64 以便将其写出来... 奇怪的。为什么不首先提取 f64