在Rust 代碼中編寫 Python 是種怎樣的體驗?

2020-12-11 CSDN

作者 | Mara Bos,Rust資深工程師

譯者 | Arvin 責編 | 屠敏

頭圖 | CSDN 下載自東方 IC

以下為譯文:

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

預覽

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

fn main() {let who = "world";let n = 5; python! {for i in range('n): print(i, "Hello", 'who)print("Goodbye") }}

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

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

運行Python代碼

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

fn main(){ println!("Hello ..."); run_python("print(\"... World!\")");}

我們可以使用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 isnot 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) in0.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) in0.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) in0.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:5note: 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) in0.27s Running `target/debug/scratchpad`Hello ...-----print("... World!") print("Bye.")----- File "<string>", line 1print("... World!") print("Bye.") ^SyntaxError: invalid syntax

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

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

空白符和符號

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

fn main() { println!("{}", stringify!( a 123b cx ( y + z )// comment ... ));}

$ cargo r Compiling scratchpad v0.1.0 Finished dev [unoptimized + debuginfo] target(s) in0.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!(ifFalse: x() y() );let b = stringify!(ifFalse: x() y() ); dbg!(a); dbg!(b); dbg!(a == b);}

$ cargo r Compiling scratchpad v0.1.0 Finished dev [unoptimized + debuginfo] target(s) in0.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]pub fn python(input: TokenStream) -> TokenStream { todo!()}

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

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

$ cargo new --lib python-macro Created library `python-macro`package

查看python-macro/Cargo.toml:

[lib]proc-macro = true

查看Cargo.toml:

[dependencies]python-macro = { path = "./python-macro" }

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

// python-macro/src/lib.rsextern crate proc_macro;useproc_macro::TokenStream;#[proc_macro]pub fn python(input: TokenStream) -> TokenStream { dbg!(input.to_string()); todo!()}

// src/main.rsusepython_macro::python;fn main() { println!("Hello ..."); python! {print("... World!")print("Bye.") }}

$ cargo r Compiling python-macro v0.1.0 Compiling scratchpad v0.1.0error[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 <https://github.com/rust-lang/rust/issues/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已經穩定下來了:你可以跳過最後幾段。^ ^)

$ sed -i '1i#![feature(proc_macro_hygiene)]' src/main.rs$ cargo r Compiling scratchpad v0.1.0[python-macro/src/lib.rs:6] input.to_string() = "print(\"... World!\") print(\"Bye.\")"error: proc macro panicked--> src/main.rs:6:5 |6 | / python! {7 | | print("... World!")8 | | print("Bye.")9 | | } | |_____^ | = help: message: not yet implementederror: aborting due to previous errorerror: could not compile `scratchpad`.

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

print("... World!") print("Bye.")

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

是時候選擇放棄了。

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

重建空白符

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

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

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

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

#![feature(proc_macro_span)]extern crate proc_macro;useproc_macro::TokenStream;#[proc_macro]pub fn python(input: TokenStream) -> TokenStream {for t in input { dbg!(t.span().start()); } todo!()}

$ 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 ininput {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.0LineColumn { line: 7, column: 8 }: printLineColumn { line: 7, column: 13 }: open ParenthesisLineColumn { line: 7, column: 14 }: "... World!"LineColumn { line: 7, column: 26 }: close ParenthesisLineColumn { line: 8, column: 8 }: printLineColumn { line: 8, column: 13 }: open ParenthesisLineColumn { line: 8, column: 14 }: "Bye."LineColumn { line: 8, column: 20 }: close Parenthesis

符合預期,太棒了!

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

#![feature(proc_macro_span)]extern crate proc_macro;useproc_macro::{TokenTree, TokenStream, LineColumn};#[proc_macro]pub fn python(input: TokenStream) -> TokenStream { let mut s = Source { source: String::new(), line: 1, col: 0, }; s.reconstruct_from(input); println!("{}", s.source); todo!()}struct Source { source: String, line: usize, col: usize,}impl Source { fn reconstruct_from(&mut self, input: TokenStream) {for t in input {if let TokenTree::Group(g) = t { let s = g.to_string();self.add_whitespace(g.span_open().start());self.add_str(&s[..1]); // the '[', '{' or '('.self.reconstruct_from(g.stream());self.add_whitespace(g.span_close().start());self.add_str(&s[s.len() - 1..]); // the ']', '}' or ')'. } else {self.add_whitespace(t.span().start());self.add_str(&t.to_string()); } } } fn add_str(&mut self, s: &str) {// Let's assume for now s contains no newlines.self.source += s;self.col += s.len(); } fn add_whitespace(&mut self, loc: LineColumn) {whileself.line < loc.line {self.source.push('\n');self.line += 1;self.col = 0; }whileself.col < loc.column {self.source.push(' ');self.col += 1; } }}

$ cargo r Compiling python-macro v0.1.0 Compiling scratchpad v0.1.0print("... 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,// <snip> start_col: Option<usize>,// <snip> let start_col = *self.start_col.get_or_insert(loc.column); let col = loc.column.checked_sub(start_col).expect("Invalid indentation.");whileself.col < col {self.source.push(' ');self.col += 1; }// <snip>

$ cargo r Compiling python-macro v0.1.0 Compiling scratchpad v0.1.0print("... 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!宏:

letsource = s.source; quote!( run_python(#source); ).into()

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

#![feature(proc_macro_hygiene)]usepython_macro::python;fn run_python(code: &str) { let py = pyo3::Python::acquire_gil();if let Err(e) = py.python().run(code, None, None) { e.print(py.python()); }}fn main() { println!("Hello ..."); python! {print("... World!")print("Bye.") }}

$ cargo r Compiling scratchpad v0.1.0 Finished dev [unoptimized + debuginfo] target(s) in0.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 翻譯,轉載請註明來源出處。

相關焦點

  • 【Rust日報】 2019-08-28:Rust異步代碼的優勢:相比於其他語言更加容易調試
    #await #tcp作者通過創建了一個TCP客戶端的項目Clobber來體驗將在1.39中穩定的Async/Await的功能,結論:體驗非常好,非常期待Rust異步穩定之後,社區將會帶來什麼變化。該文章簡單介紹了Rust中使用TreeSitter的方法。Crev的使用教程#Crevcargo-crev是一個代碼審查工具,旨在構建信任的生態網絡。但它並不局限於Rust社區,C/Cpp也可以使用。該工具可以判斷你項目中依賴crate的安全性、質量和發現的問題。可以在公共的git倉庫裡發布可驗證的review信息。
  • Python編寫代碼的規範要求
    打開APP Python編寫代碼的規範要求 碼農阿勇 發表於 2020-01-16 17:44:00 在我們日常生活中,做什麼事情講究規矩
  • @Python 開發者,如何更加高效地編寫代碼?
    對於 Python 開發者而言,Anaconda 能省下大量時間下載和安裝模塊包、處理項目環境等問題,幫助開發者更加愉快地編寫代碼。如果你苦於給 Python 安裝各種包,安裝過程中還各種出錯。那麼我牆裂推薦——Anaconda,它可以幫助你管理這些包,包括安裝、卸載、更新。
  • 學術界開始從Python轉向Rust
    Rust 融合了 C++ 語言的性能與其他高級語言優秀的語法特性,對代碼安全性問題也進行了特別的處理,Mozilla 的火狐瀏覽器有一部分就是用 Rust 編寫的,微軟也正在用它重新編碼 Windows 作業系統的部分內容。
  • 「Rust語言」以為你喜歡Python?等到你遇見Rust
    雖然用具有動態類型的語言編寫軟體要容易得多,但代碼可能很快變得難以維護。這就是為什麼Python代碼比C代碼更難維護的原因之一。 另一方面,必須聲明每個變量的類型C-style會非常煩人。如果你曾經嘗試在返回C浮點類型的函數中使用double,你就會明白我的意思。
  • 如何編寫和運行Python程序
    第一種方式是進入Pyhton的安裝目錄,直接運行python.exe程序;第二種方式是進入Windows命令行窗口,在命令行窗口啟動python.exe。在Windows命令行窗口啟動Python交互式解釋器,首先需要將Python安裝目錄的路徑,添加到Path系統環境變量。否則,只能進入Python安裝目錄啟動交互式解釋器。
  • 如何編寫簡潔美觀的Python代碼
    介紹你有沒有遇到過一段寫得很糟糕的Python代碼?我知道你們很多人都會點頭的。編寫代碼是數據科學家或分析師角色的一部分。另一方面,編寫漂亮整潔的Python代碼完全是另一回事。作為一個精通分析或數據科學領域(甚至軟體開發)的程式設計師,這很可能會改變你的形象。
  • Rust 中的錯誤處理 - Rust 實踐指南
    對於程式設計師來說,錯誤處理的重要性是不言而喻的,貫穿於代碼編寫、開發、調試,以及交付運行的全過程中。
  • 如何在Python中編寫簡單代碼,並且速度超越Spark?
    如果你想在Python中編寫簡單代碼,並且用比Spark更快的速度運行,同時無需重新編碼、無需開發者解決部署、擴展和監控問題,可能嗎?你可能會「說我是一個夢想家」。我是一個夢想家,但不是唯一的一個!當我們添加前文示例中使用的json和pandas處理(Notebook:https://github.com/nuclio/rapids/blob/master/demo/python-agg.ipynb)時,性能會進一步降低,處理速度僅為18MB / s。那麼,是否需要回到Spark進行流處理呢?不,等等。
  • 我們為什麼選擇Rust開發頂尖實時通信產品?|應用程式|代碼|編譯器|...
    除了這個私有 API 外,我們在工作區中還有獨立的板條箱,我們將這些板條箱視為開放原始碼的候選人。例如,我們已經自行編寫了適合長期運行的高吞吐量 actor 的 actor 框架,以及用於可靠、高帶寬、低延遲媒體流的網絡協議。  我們將不同的二進位文件用於 tonari 系統的不同部分,並且每個二進位文件都位於 binaries 中。
  • 讓Python代碼更快運行的 5 種方法
    如果你想讓Python在同一硬體上運行得更快,你有兩個基本選擇,而每個都會有一個缺點:·您可以創建一個默認運行時所使用的替代語言(CPython的實現)——一個主要的任務,但它最終只會是CPython的一個簡易替代者。·您也可以利用某些速度優化器重寫現有Python代碼,這意味著程式設計師要花更多精力編寫代碼,但不需要在運行時加以改變。
  • Rust 能取代 Python,更好的實現神經網絡?
    這個過程非常繁瑣,所以我另寫了一篇文章專門討論(https://ngoldbaum.github.io/posts/loading-mnist-data-in-rust/)。在這之後,下一步我們必須弄清楚如何用Rust表示Python代碼中的Network類。
  • 為什麼選擇Rust?
    它可能看起來會妨礙你編寫高效且富有表現力的代碼,但令人驚訝的是,恰恰相反:編寫一個有效且地道的Rust程序實際上比編寫一個有潛在危險的程序更容易。在後一種情況下,你將與編譯器發生衝突,因為你嘗試的幾乎所有操作都會導致內存安全問題。上圖右側部分顯示了並發性和內存安全相關的問題,這些問題根源上不可能發生在常規(非unsafe塊)Rust代碼中。
  • 極速體驗|VS Code+Python敏捷開發
    VS Code在前端開發中,有一個非常好用的工具——Visual Studio Code,簡稱VS code。很多人使用後都會感嘆「用VS Code 寫代碼是真好用、真爽。」它是一款當下流行、十分出色的ide開發工具。
  • 通過編寫一個簡單的遊戲來學習 Rust | Linux 中國
    因為程式語言通常具有相似性,一旦你懂了一種語言,你就可以通過理解其差異來學習另一種語言的基礎知識。學習新語言的一個好方法是使用一個你可以用來練習的標準程序。這可以讓你專注於語言,而不是程序的邏輯。我在這一系列文章中使用了一個「猜數字」的程序,在這個程序中,電腦會在 1 到 100 之間選一個數字讓你猜。程序一直循環,直到你猜對數字為止。
  • 科普文,python注釋,在代碼中對代碼功能進行解釋的標註性文字
    提高代碼的可讀性,需要提供對代碼的注釋。 python注釋,主要作用在代碼中,對代碼功能進行解釋,是一種標註性文字。一般情況下分成三類,單行注釋、多行注釋、中文聲明注釋。一、先說單行注釋!如圖所示!分為兩種情況。1、單行注釋放在要注釋代碼的前一行。2、注釋放在代碼右側。
  • 5種方法,加密你的Python代碼
    其中一個缺點,讓不少開發者頭疼不已,由於Python解釋器開源的關係,導致Python代碼無法加密,代碼的安全性得不到保障。當然,想要加密Python代碼,也並非無解。最常見的加密方式有4種,還有1種獨特的加密方式。
  • 代碼整潔之道-編寫 Pythonic 代碼
    對此呢,我特意收集了一些適合所有學習 Python 的人,代碼整潔之道。寫出 Pythonic 代碼談到規範首先想到就是 Python 有名的 PEP8 代碼規範文檔,它定義了編寫Pythonic代碼的最佳實踐。可以在 https://www.python.org/dev/peps/pep-0008/ 上查看。
  • Python編寫代碼規範-幫你寫出優雅的代碼
    在工作或實習中,掌握規範的代碼編寫能力是對代碼編寫人員的基本要求。
  • 代碼詳解:如何用Python運行高性能的數學範式?
    全文共1140字,預計學習時長3分鐘比較python中不同運行範式的表現情況那麼,在開發和學習中有哪些技巧呢?首先需要明確的是:編寫python代碼和編寫pythonic代碼之間存在很大差異。這篇文章圍繞一些最常用的數據科學操作編寫了最佳(也存在爭議的)實踐。