日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

c++hello world代码_在 Rust 代码中编写 Python 是种怎样的体验?

發布時間:2023/12/19 c/c++ 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c++hello world代码_在 Rust 代码中编写 Python 是种怎样的体验? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | Mara Bos,Rust資深工程師譯者 | Arvin 責編 | 屠敏頭圖 | CSDN 下載自東方 IC出品 | CSDN(ID:CSDNnews)

以下為譯文:

大約一年前,我發布了一個名為inline-python(https://crates.io/crates/inline-python)的Rust類庫,它允許大家使用python!{ .. }宏輕松地將一些Python混合到Rust代碼中。在本系列中,我將從頭展示開發此類庫的過程。

預覽

如果不熟悉inline-python類庫,你可以執行以下操作:

main()?{

它允許你將Python代碼直接嵌入Rust代碼行之間,甚至直接在Python代碼中使用Rust變量。

我們將從一個比這個簡單得多的案例開始,然后逐步努力以達到這個結果(甚至更多!)。

運行Python代碼

首先,讓我們看一下如何在Rust中運行Python代碼。讓我們嘗試使第一個簡單的示例生效:

fn?main()?{

我們可以使用std::process::命令來運行python可執行文件并傳遞python代碼,從而實現run_python,但如果我們希望能夠定義和讀回Python變量,那么最好從使用PyO3庫開始。

PyO3為我們提供了Python的Rust綁定。它很好地包裝了Python C API,使我們可以直接在Rust中與各種Python對象交互。(甚至在Rust中編寫Python庫,但這是另一個主題。)

它的Python::run?功能完全符合我們的需求。它將Python代碼作為&str,并允許我們使用兩個可選的PyDicts 來定義范圍內的任何變量。讓我們試一試吧:

fn?run_python(code:?&str)?{
????let?py?=?pyo3::Python::acquire_gil();?//?Acquire?the?'global?interpreter?lock',?as?Python?is?not?thread-safe.
????py.python().run(code,?None,?None).unwrap();?//?No?locals,?no?globals.
}
$?cargo?run
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.29s
?????Running?`target/debug/scratchpad`
Hello?...
...?World!

看,這就成功了!

基于規則的宏

在字符串中編寫Python不是最便捷的方法,所以我們嘗試改進它。宏允許我們在Rust中自定義語法,所以讓我們嘗試一下:

fn?main()?{
????println!("Hello?...");
????python!?{
????????print("...?World!")
????}
}

宏通常是使用macro_rules!進行定義,您可以基于標記和表達式之類的內容使用高級“查找和替換”規則來定義宏。(有關macro_rules!的介紹請參見Rust Book中有關宏的章節,有關Rust宏所有的細節都可以在《Rust宏的小書》中找到。)

由macro_rules!定義的宏在編譯時無法執行任何代碼,這些宏僅是應用了基于模式的替換規則。它非常適合vec![],甚至是lazy_static!{ .. },但對于解析和編譯正則表達式(例如regex!("a.*b"))之類的功能而言,還不夠強大。

在宏的匹配規則中,我們可以匹配表達式,標識符,類型和許多其他內容。由于“有效的Python代碼”不是一個選項,所以我們只能讓宏接受所有內容:大量的原始的符號:

macro_rules!?python?{
????($($code:tt)*)?=>?{
????????...
????}
}

(有關macro_rules!工作原理的詳細信息,請參見上面鏈接的資源。)

對宏的調用應該產生run_python(".."),這是一個包裹了所有Python代碼的字符串文本。幸運的是:有一個內建宏為我們把內容放到一個字符串里,叫做stringify!,因此我不必從頭開始。

macro_rules!?python?{
????($($code:tt)*)?=>?{
????????run_python(stringify!($($code)*));
????}
}

結果如下:

$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.32s
?????Running?`target/debug/scratchpad`
Hello?...
...?World!

如愿以償得到了期望結果!

但是,如果我們有不止一行的Python代碼會怎樣?

fn?main()?{
????println!("Hello?...");
????python!?{
????????print("...?World!")
????????print("Bye.")
????}
}
$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.31s
?????Running?`target/debug/scratchpad`
Hello?...
thread?'main'?panicked?at?'called?`Result::unwrap()`?on?an?`Err`?value:?PyErr?{?type:?Py(0x7f1c0a5649a0,?PhantomData)?}',?src/main.rs:9:5
note:?run?with?`RUST_BACKTRACE=1`?environment?variable?to?display?a?backtrace

很不幸,我們失敗了。

為了進行調試,我們需要正確輸出PyErr,并顯示我們傳遞給Python::run的確切Python代碼:

fn?run_python(code:?&str)?{
????println!("-----");
????println!("{}",?code);
????println!("-----");
????let?py?=?pyo3::Python::acquire_gil();
????if?let?Err(e)?=?py.python().run(code,?None,?None)?{
????????e.print(py.python());
????}
}
$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.27s
?????Running?`target/debug/scratchpad`
Hello?...
-----
print("...?World!")?print("Bye.")
-----
??File?"",?line?1
????print("...?World!")?print("Bye.")
????????????????????????^
SyntaxError:?invalid?syntax

很顯然,兩行Python代碼落在同一行,在Python中這是無效的語法。

現在我們遇到了必須克服的最大問題:stringify!把空白符搞亂了.

空白符和符號

讓我們仔細研究一下stringify!:

fn?main()?{$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.21s
?????Running?`target/debug/scratchpad`
a?123?b?c?x(y?+?z)?...

它不僅刪除了所有不必要的空格,還刪除了注釋。因為它的工作原理是處理單詞(token),不再是源代碼里面的:a,123,b等。

Rustc編譯器做的第一件事就是將源代碼分為單詞,這使得解析后的工作更容易進行,不必處理諸如1,2,3,這樣的個別字符,只需處理諸如“integer literal 123”這樣的單詞。另外,空白和注釋在分詞之后就消失了,因為它們對編譯器來說沒有意義。

stringify!()是一種將一串單詞轉換回字符串的方法,但它是基于“最佳效果”的:它將單詞轉換回文本,并且僅在需要時才在單詞周圍插入空格(以避免將b、c轉換為bc)。

所以這是一個死胡同。Rustc不小心把寶貴的空白符丟掉了,但這在Python中非常重要。

我們可以嘗試猜測一下哪些代碼的空格必須用換行符代替,但是縮進肯定會成為一個問題:

fn?main()?{
????let?a?=?stringify!(
????????if?False:
????????????x()
????????y()
????);
????let?b?=?stringify!(
????????if?False:
????????????x()
????????????y()
????);
????dbg!(a);
????dbg!(b);
????dbg!(a?==?b);
}
$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.20s
?????Running?`target/debug/scratchpad`
[src/main.rs:12]?a?=?"if?False?:?x()?y()"
[src/main.rs:13]?b?=?"if?False?:?x()?y()"
[src/main.rs:14]?a?==?b?=?true

這兩個Python代碼片段有不同的含義,但是stringify!給了我們相同的結果。

在放棄之前,讓我們嘗試一下其他類型的宏。

過程宏

Rust的過程宏是定義宏的另一種方法。盡管macro_rules!只能定義“函數樣式的宏”(帶有!標記的宏),過程宏也可以定義自定義派生宏(例如#[derive(Stuff)])和屬性宏(例如#[stuff])。

過程宏是作為編譯器插件實現的。您需要編寫一個函數,該函數可以訪問編譯器看到的單詞流,然后就可以執行所需的任何操作,最后需要返回一個新的單詞流供編譯器使用(或者用于自定義的用途):

#[proc_macro]

上述單詞流不夠好。因為我們需要源代碼,而不僅僅是單詞。雖然目前還沒有成功,但是讓我們繼續吧,也許過程宏更大的靈活性能夠解決問題。

由于過程宏在編譯過程中運行Rust代碼,因此它們需要使用單獨的proc-macro類庫中,這個類庫在您編譯其他內容之前已經被編譯好。

new?--lib?python-macro

查看python-macro/Cargo.toml:

[lib]

查看Cargo.toml:

[dependencies]

讓我們從一個只有panics (todo!())的實現開始,在輸出TokenStream之后:

//?python-macro/src/lib.rs//?src/main.rs$?cargo?r
???Compiling?python-macro?v0.1.0
???Compiling?scratchpad?v0.1.0
error[E0658]:?procedural?macros?cannot?be?expanded?to?statements
?-->?src/main.rs:5:5
??|
5?|?/?????python!?{
6?|?|?????????print("...?World!")
7?|?|?????????print("Bye.")
8?|?|?????}
??|?|_____^
??|
??=?note:?see?issue?#54727??for?more?information
??=?help:?add?`#![feature(proc_macro_hygiene)]`?to?the?crate?attributes?to?enable

天啊,這里發生了什么?

Rust錯誤為“?過程宏不能擴展為語句?”,以及有關啟用“hygienic macros”的內容。Macro hygiene是Rust宏的出色功能,不會意外地將任何名稱“泄漏”給外界(反之亦然)。如果宏擴展使用了名為的x的臨時變量,則它將與宏外部的任何代碼中出現的變量x分開。

但是,此功能對于過程宏還不穩定。因此,過程宏除了作為一個單獨的項(例如在文件范圍內,但不在函數內)之外,不允許出現在任何地方。

接下來,我們會發現存在一個非常可怕但令人著迷的解決方法—讓我們啟用實驗功能#![feature(proc_macro_hygiene)]并繼續我們的冒險。

(如果你將來讀到這篇文章時,proc_macro_hygiene已經穩定下來了:你可以跳過最后幾段。^ ^)

'1i#![feature(proc_macro_hygiene)]'?src/main.rs

在向我們展示了它的字符串輸入參數之后,我們的過程宏即如預期般地崩潰了:

print(

正如預期的那樣,空白符再次被丟棄了。:(

是時候選擇放棄了。

不過或者..也許有一種方法可以解決這個問題。

重建空白符

盡管rustc編譯器只在解析和編譯時使用單詞,但是在某種程度上它仍然可以準確地知道何時報告錯誤。單詞中沒有換行符,但是它仍然知道我們的錯誤發生在第6到第9行。那它如何做到的?

事實證明,單詞中包含很多信息。它們包含一個Span,是單詞在源文件中的開始和結束的位置。Span可以告訴單詞在哪個文件、行和列編號處開始和結束。

如果我們能夠得到這些信息,我們就可以通過在單詞之間放置空格和換行符來重新構造空白符,以匹配它們的行和列信息。

提供這些信息的函數還不穩定,而且還沒有#![feature(proc_macro_span)]。讓我們啟用它,看看我們得到了什么:

#![feature(proc_macro_span)]$?cargo?r
???Compiling?python-macro?v0.1.0
???Compiling?scratchpad?v0.1.0
[python-macro/src/lib.rs:9]?t.span().start()?=?LineColumn?{
????line:?7,
????column:?8,
}
[python-macro/src/lib.rs:9]?t.span().start()?=?LineColumn?{
????line:?7,
????column:?13,
}
[python-macro/src/lib.rs:9]?t.span().start()?=?LineColumn?{
????line:?8,
????column:?8,
}
[python-macro/src/lib.rs:9]?t.span().start()?=?LineColumn?{
????line:?8,
????column:?13,
}

真棒!我們得到了一些數據。

但是只有四個單詞了。原來("... World!") 這里只出現一個單詞,而不是三個((,"... World!",和))。如果看一下TokenStream的文檔,我們會發現它并沒有提供單詞流,而是單詞樹。顯然,Rust的詞法分析器已經匹配了括號(以及大括號和方括號),并且它不僅給出了線性的單詞列表,而且還給出了單詞樹。括號內的單詞可以看成是某個單詞組的后代。

讓我們修改過程宏以遞歸地遍歷組內的所有單詞(并改進一下輸出):

#[proc_macro]
pub?fn?python(input:?TokenStream)?->?TokenStream?{
????print(input);
????todo!()
}

fn?print(input:?TokenStream)?{
????for?t?in?input?{
????????if?let?TokenTree::Group(g)?=?t?{
????????????println!("{:?}:?open?{:?}",?g.span_open().start(),?g.delimiter());
????????????print(g.stream());
????????????println!("{:?}:?close?{:?}",?g.span_close().start(),?g.delimiter());
????????}?else?{
????????????println!("{:?}:?{}",?t.span().start(),?t.to_string());
????????}
????}
}
$?cargo?r
???Compiling?python-macro?v0.1.0
???Compiling?scratchpad?v0.1.0
LineColumn?{?line:?7,?column:?8?}:?print
LineColumn?{?line:?7,?column:?13?}:?open?Parenthesis
LineColumn?{?line:?7,?column:?14?}:?"...?World!"
LineColumn?{?line:?7,?column:?26?}:?close?Parenthesis
LineColumn?{?line:?8,?column:?8?}:?print
LineColumn?{?line:?8,?column:?13?}:?open?Parenthesis
LineColumn?{?line:?8,?column:?14?}:?"Bye."
LineColumn?{?line:?8,?column:?20?}:?close?Parenthesis

符合預期,太棒了!

現在要重建空白符,如果我們不在正確的行中,我們需要插入換行符,如果我們不在正確的列中,則需要插入空格。讓我們來看看效果:

#![feature(proc_macro_span)]$?cargo?r
???Compiling?python-macro?v0.1.0
???Compiling?scratchpad?v0.1.0






????????print("...?World!")
????????print("Bye.")
error:?proc?macro?panicked

看來這是行得通的,但是這些額外的換行符和空格又是怎么回事?對比下源文件,這是對的,第一個標記從第7行第8列開始,因此它正確地將print放在第8列的第7行。我們要查找的位置正是.rs文件中的確切位置。

開始時多余的換行符不是問題(空行在Python中無效)。它甚至具有很好的副作用:當Python報告錯誤時,它報告的行號將與.rs文件中的行號匹配。

但是,這8個空格是個問題。盡管我們內部的Python代碼python!{..}相對于Rust代碼是適當縮進的,但我們提取的Python代碼應以“零”縮進級別開始。否則,Python將發生無效縮進的錯誤。

讓我們從所有列號中減去第一個標記的列號:

start_col:?None,
????//?
????start_col:?Option,//?
????let?start_col?=?*self.start_col.get_or_insert(loc.column);
????let?col?=?loc.column.checked_sub(start_col).expect("Invalid?indentation.");while?self.col?????????self.source.push('?');self.col?+=?1;
????}//?$?cargo?r
???Compiling?python-macro?v0.1.0
???Compiling?scratchpad?v0.1.0






print("...?World!")
print("Bye.")
error:?proc?macro?panicked

結果太棒了!

現在,我們只需要把這個字符串轉換為字符串文字標記?并將其放在run_python();周圍即可:

?TokenStream::from_iter(vec![
????????TokenTree::from(Ident::new("run_python",?Span::call_site())),
????????TokenTree::Group(Group::new(
????????????Delimiter::Parenthesis,
????????????TokenStream::from(TokenTree::from(Literal::string(&s.source))),
????????)),
????????TokenTree::from(Punct::new(';',?Spacing::Alone)),
????])

太糟糕了,直接使用TokenTree太困難了,尤其是從頭開始制作trees和streams。

如果只有一種方法可以編寫我們要生成的Rust代碼,那就只能是quote類庫的quote!宏:

let?

現在使用我們的原始run_python函數對其進行測試:

#![feature(proc_macro_hygiene)]$?cargo?r
???Compiling?scratchpad?v0.1.0
????Finished?dev?[unoptimized?+?debuginfo]?target(s)?in?0.31s
?????Running?`target/debug/scratchpad`
Hello?...
...?World!
Bye.

終于成功了!

封裝成類庫

現在我們把它變成一個可重用的庫,:

  • 刪除fn main,

  • 重命名main.rs為lib.rs,

  • 給類庫起個好名字,例如inline-python,

  • 公開run_python,

  • 更改quote!()中的run_python調用改為::inline_python::run_python,同時添加pub python_macro::python;從python!這個類庫中重新導出宏。

下一步計劃

可能還有很多內容需要改進,還有很多錯誤需要發現,但是至少我們現在可以在Rust代碼行之間運行Python片段了。

目前最大的問題是,這還不是很有用,因為沒有數據可以(輕松)越過Rust-Python的邊界。

在第2部分中,我們將研究如何使Rust變量用于Python代碼。

更新:在等待第2部分的同時,還有第1A部分,只是它沒有改進我們的python!{}宏,但涉及了人們向我詢問的一些細節。具體來說,它涉及:

  • 為什么要像這樣在Rust內部使用Python,

  • 語法問題,例如使用Python的單引號字符串

  • 使用Span::source_text的選項,當我第一次編寫這段代碼時,它其實還不存在。

原文:https://blog.m-ou.se/writing-python-inside-rust-1/

本文為 CSDN 翻譯,轉載請注明來源出處。

更多精彩推薦

?AI 世界的硬核之戰,Tengine 憑什么成為最受開發者歡迎的主流框架?

?說了這么多 5G,最關鍵的技術在這里

?360金融新任首席科學家:別指望AI Lab做成中臺

?AI圖像智能修復老照片,效果驚艷到我了

?程序員內功修煉系列:10 張圖解談 Linux 物理內存和虛擬內存

?當 DeFi 遇上 Rollup,將擦出怎樣的火花?

你點的每個“在看”,我都認真當成了喜歡

總結

以上是生活随笔為你收集整理的c++hello world代码_在 Rust 代码中编写 Python 是种怎样的体验?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。