pythonをrustへ変換したときの対応をまとめる
もともとpythonのアプリがあって、それをrustへ書き直す作業をしていたことがありまして、
この記事ではその際につまづいた変換部分をまとめておきます。(随時追加予定)
それぞれの言語で特徴や目的が違うので、機能が対応しているわけではないです。
(例:pythonのdictをrustのstructに対応させるなど)
・python:処理が最後まで動けばOK。
・rust:テストが動けばよし。(またrustの経験が浅いので、技術的に難しいものは使っていません)
注意点
pythonの動的型付けとrustの静的型付けの違いは大きく、そのまま変換してもうまく動かないことがあります。
その場合、無理にrust側で柔軟な構造を定義するよりも、python側を修正する方が結果的に可読性の高いコードになることが多かったです。
(rust側で複雑な処理を作る必要が出てきたら、技術的負債がたまっているかも)
python dict 動的にkeyが決まる
config = {
"key1": "value1",
"key2": "value2",
}
print(config["key1"])
let config = HashMap::from([
("key1", "value1"),
("key2", "value2"),
]);
println!("{}", config["key1"]);
python dict 静的にkeyが決まる
dictのkeyが静的に決まる場合はrustのstructで定義してしまった方がvscodeの補完機能が効くので便利
config = {
"key1": ["value1_1","value1_2","value1_3"],
"key2": ["value2_1","value2_2","value2_3"],
}
print(config["key1"])
struct Config<'a> {
key1: Vec<&'a str>,
key2: Vec<&'a str>,
}
let config = Config {
key1: vec!["value1_1", "value1_2", "value1_3"],
key2: vec!["value2_1", "value2_2", "value2_3"],
};
println!("{:?}", config.key1);
python structured arrayの各行を繰り返し処理
pythonのpandasやnumpyでデータハンドリングをしているものについては、rustのstructによってrecordレベルの定義をしておく
import numpy as np
data = np.array([
(1, 'Alice', 25),
(2, 'Bob', 30),
(3, 'Charlie', 35)
], dtype=[('id', 'i4'), ('name', 'U10'), ('age', 'i4')])
for row in data:
print(row['id'],row['name'],row['age'])
pub struct DataFrame {
pub rec: Vec<DtypesPerson>,
}
pub struct DtypesPerson {
id: i32,
name: String,
age: i32,
}
let data = DataFrame {
rec: vec![
DtypesPerson { id: 1, name: String::from("Alice"), age: 25 },
DtypesPerson { id: 2, name: String::from("Bob"), age: 30 },
DtypesPerson { id: 3, name: String::from("Charlie"), age: 35 },
],
};
for row in &data.rec {
println!("{} {} {}", row.id, row.name, row.age);
}
python if 単一の変数
x = 10
if x > 5:
y = 20
else:
y = 0
print(y)
let x = 10;
let y = if x > 5 {
20
} else {
0
};
println!("{}", y);
python if 複数の変数
x = 10
y = 0
z = 5
if x > 5:
y = 20
z = 30
else:
y = 0
print(y,z)
let x = 10;
let mut y = 0;
let mut z = 5;
if x > 5 {
y = 20;
z = 30;
} else {
y = 0;
}
println!("{} {}", y, z);
python def 関数
import numpy as np
data = np.array([
(1, 'Alice', 25, ''),
(2, 'Bob', 30, ''),
(3, 'Charlie', 35, '')
], dtype=[('id', 'i4'), ('name', 'U10'), ('age', 'i4'), ('greeting', 'U10')])
def add_greeting_column(data, suffixe):
data["greeting"] = [i + suffixe for i in data["name"]]
return data
suffixe = "!!!"
data_new = add_greeting_column(data, suffixe)
for row in data_new:
print(row)
> (1, 'Alice', 25, 'Alice!!!')
> (2, 'Bob', 30, 'Bob!!!')
> (3, 'Charlie', 35, 'Charlie!!!')
↑のpythonコードではnumpyのdata_newを新たに作成しているけど、実は引数側のdataも書き変わっている
↓のrustだと所有権がdataからdata_newへ移動しているので、dataを再利用を防止できる
pub struct DataFrame {
pub rec: Vec<DtypesPerson>,
}
pub struct DtypesPerson {
id: i32,
name: String,
age: i32,
greeting:String,
}
fn add_greeting_column(df: DataFrame,suffix: &String) -> DataFrame {
let mut new_data = Vec::new();
for row in &df.rec {
new_data.push(DtypesPerson {
id: row.id,
name: row.name.clone(),
age: row.age,
greeting: row.name.clone() + &suffix,
});
}
DataFrame { rec :new_data}
}
let data = DataFrame {
rec: vec![
DtypesPerson { id: 1, name: String::from("Alice"), age: 25, greeting: String::from("")},
DtypesPerson { id: 2, name: String::from("Bob"), age: 30, greeting: String::from("")},
DtypesPerson { id: 3, name: String::from("Charlie"), age: 35, greeting: String::from("")},
],
};
let suffix = "!!!".to_string();
let data_new = add_greeting_column(data, &suffix);
for row in &data_new.rec {
println!("{:?} {:?} {:?} {:?}", row.id, row.name, row.age, row.greeting);
};
// 1 "Alice" 25 "Alice!!!"
// 2 "Bob" 30 "Bob!!!"
// 3 "Charlie" 35 "Charlie!!!"
主となる引数が1つあるようなケースだと、rustの所有権を移動する形で使うことが多い。(上の例だとdata:DataFrame)
そのほかちょっとした設定などの補助的なものは、それほど意識して区別していない(上の例だとsuffix)
Discussion