rust python對比_Python Rust 迭代器對比

迭代是數據處理的基石,而 Python 中所有集合都可以迭代,這是 Python 讓使用者感到非常方便的特征之一。

下面是一些在 Python 中經常使用的迭代模式

# 列表

for i in [1, 2, 3, 4]:

print(i)

# 字典

di = {'a': 1, 'b': 2, 'c': 3}

# 迭代鍵

for k in di.keys():

print(k)

# 迭代鍵值

for k, v in di.items():

print('{}: {}'.format(k, v))

除了基本數據類型,Python 也支持為自定義的數據類型實現迭代器協議。Python 解釋器在需要迭代對象 x 時會自動調用 iter(x)。

內置的 iter 函數有如下作用。

檢查對象是否實現了 __iter__ 方法,如果實現了就調用它,__iter__ 方法返回一個迭代器

如果沒有實現 __iter__ 方法,但是實現了 __getitem__ 方法,Python 會創建一個迭代器,嘗試按順序(從索引0)獲取元素。

如果上述兩個嘗試失敗,Python 會拋出 TypeError 異常,提示該元素不可迭代。

所以如果我們要讓某個對象是可迭代對象,只需要實現 __iter__,這個方法要求返回一個迭代器,那什么是迭代器呢? Python 中標準的迭代器接口有兩個方法。

__next__

返回下一個可用的元素,如果元素,拋出 StopIteration 異常。

__iter__

返回迭代器自身,即 self,以便在應該使用可迭代對象的地方使用迭代器,如 for 循環中。

這里需要說明的一點是,可迭代對象與迭代器是不同的,《流暢的 Python》這樣定義可迭代對象

> 使用 iter 內置函數可以獲取迭代器的對象。如果對象實現了能返回迭代器的 iter 方法,那么對象就是可迭代的。序列都可以迭代;實現了 getitem 方法,而且其參 數是從零開始的索引,這種對象也可以迭代

>

而迭代器則定義為

> 迭代器是這樣的對象:實現了無參數的 next 方法,返回序列中的下一個元素;如 果沒有元素了,那么拋出 StopIteration 異常。Python 中的迭代器還實現了 iter 方 法,因此迭代器也可以迭代。

也就是說每次對可迭代對象調用 iter(x) 都將返回一個新的迭代器。

那如果為一個可迭代對象實現 __next__ 方法,即把這個可迭代對象變成自身的可迭代對象會怎樣呢?沒人阻止你這樣做,但當你真正為這個對象實現這兩個方法時,你會發現麻煩不斷。舉個例子

class MyData:

def __init__(self, values):

# 假設 value 為列表

self.values = values

def __iter__(self):

return self

def __next__(self):

# ???

raise NotImplementedError()

按照協議 __next__ 應該返回下一個元素或者拋出 StopIteration,顯然我們需要一個屬性存儲當前迭代位置,所以應該似乎應該這樣寫

class MyData:

def __init__(self, values):

self.values = values

# 記錄當前迭代位置

self.current = 0

def __iter__(self):

# 每次調用重頭開始迭代

self.current = 0

return self

def __next__(self):

if self.current < len(self.values):

value = self.values[self.current]

self.current += 1

return value

else:

raise StopIteration

但考慮這樣一種情況,我們調用2次 iter,交替迭代獲得的2個迭代器,預期行為應該是2個迭代器不會干涉,但如果按上述代碼實現 MyData 對象行為并不符合預期。

data = MyData([1, 2, 3, 4, 5])

data_iter1 = iter(data)

print(next(data_iter1)) # 結果為1

print(next(data_iter1)) # 結果為2

data_iter2 = iter(data)

print(next(data_iter2)) # 結果為1

print(next(data_iter1)) # 預期為3,但得到2

如果把 current 屬性變為列表,每次調用 iter 增加一個元素表示新的迭代器當前位置呢?但又會導致 __next__ 變得非常復雜,因為它必須找到不同迭代器對應當前位置,這樣才能保證正確的迭代行為。為什么我們的迭代實現如此復雜呢?根本原因在于 __iter__ 總是返回自身,換言之,調用 iter 的迭代器都是一樣,這其實破壞了 每次調用 iter 返回新的迭代器 這一設計。

解決難題辦法很簡單,遵循設計,把可迭代對象和迭代器拆開。

class MyData:

def __init__(self, values):

self.values = values

def __iter__(self):

return DataIterator(list(self.values))

class DataIterator:

def __init__(self, values):

self.values = values

self.current = 0

def __iter__(self):

return self

def __next__(self):

if self.current < len(self.values):

value = self.values[self.current]

self.current += 1

return value

else:

raise StopIteration

現在 __iter__ 將會返回新的迭代器,每個迭代器都保存著自身狀態,這讓我們不必費心費力第維護迭代器狀態。

所以,把可迭代對象變成其自身的迭代器是條歧路,反設計的。

在 Rust 中,迭代也遵循著相似的設計,Rust 中實現了 Iterator 特性的結構體就被認為是可迭代的。

我們可以像 Python 那樣使用 for 循環迭代

let v1 = vec![1, 2, 3, 4, 5];

for item in v1 {

println!("{}", item);

}

std::iter::Iterator 只要求實現 next 方法即可,下面是一個官方文檔中的例子

// 首先定義一個結構體,作為“迭代器”

struct Counter {

count: usize,

}

// 實現靜態方法 new,相當于構造函數

// 這個方法不是必須的,但可以讓我更加方便

// 地使用 Counter

impl Counter {

fn new() -> Counter {

Counter { count: 0 }

}

}

// 實現 Iterator 特性

impl Iterator for Counter {

// 確定迭代器的返回值類型

type Item = usize;

// 只有 next() 是必須實現的方法

// Option 也可以寫成 Option<:item>

fn next(&mut self) -> Option {

// 增加計數

self.count += 1;

// 到 5 就返回 :)

if self.count < 6 {

Some(self.count)

} else {

None

}

}

}

let mut counter = Counter::new();

let x = counter.next().unwrap();

println!("{}", x);

let x = counter.next().unwrap();

println!("{}", x);

let x = counter.next().unwrap();

println!("{}", x);

let x = counter.next().unwrap();

println!("{}", x);

let x = counter.next().unwrap();

println!("{}", x);

與 for 循環使用時,Python 使用 StopIteration 告訴編譯是時候定制循環了,在 Rust 則是 None,所以 next 方法返回值為 Option<:item>。其實使用 for 循環是一種語法糖

let values = vec![1, 2, 3, 4, 5];

for x in values {

println!("{}", x);

}

去掉語法糖后相當于

let values = vec![1, 2, 3, 4, 5];

{

let result = match IntoIterator::into_iter(values) {

mut iter => loop {

let next;

match iter.next() {

Some(val) => next = val,

None => break,

};

let x = next;

let () = { println!("{}", x); };

},

};

result

}

編譯器會對 values 調用 into_iter 方法,獲取迭代器,接著匹配迭代器,一次又一次地調用迭代器的 next 方法,直到返回 None,這時候終止循環,迭代結束。

這里又涉及到另一個特性 std::iter::IntoIterator,這個特性可以把某些東西變成一個迭代器。

IntoInterator 聲明如下:

pub trait IntoIterator

where

<:intoiter as iterator>::Item == Self::Item,

{

type Item;

type IntoIter: Iterator;

fn into_iter(self) -> Self::IntoIter;

}

類比于 Python 中的概念,可以做出以下結論:

實現了 IntoIterator 特性的結構體是一個“可迭代對象”

實現了 Iterator 特性的結構體一個“迭代器”

for 循環會嘗試調用結構的 into_iter 獲得一個新的“迭代器”,當迭代器返回 None 時提示迭代結束

基于以上結論,我們可以實現 Python 例子中類似的代碼

#[derive(Clone)]

struct MyData{

values: Vec,

}

struct DataIterator {

current: usize,

data: Vec,

}

impl DataIterator {

fn new(values: Vec) -> DataIterator {

DataIterator {

current: 0,

data: values

}

}

}

impl Iterator for DataIterator {

type Item = i32;

fn next(&mut self) -> Option {

if self.current < self.data.len() {

let ret = Some(self.data[self.current]);

self.current += 1;

ret

} else {

None

}

}

}

impl IntoIterator for MyData {

type Item = i32;

type IntoIter = DataIterator;

fn into_iter(self) -> DataIterator {

DataIterator::new(self.values)

}

}

fn main() {

let data = MyData { values: vec![1, 2, 3, 4] };

for item in data {

println!("{}", item);

}

}

總結

Rust 不愧是一門多范式的現代編程語言,如果你之前對某個語言有相當深入的了解,在學習 Rust 是總會有“喔,這不是xxx嗎”的感覺。雖然之前閱讀過 《流暢的Python》,但在可迭代對象與迭代器這一章并沒有太多影響,因為在使用 Python 時真正要我實現迭代接口的場景非常少;直到最近學習 Rust,在嘗試使用 Rust 的 Iterator 特性為我的結構實現與 for 循環交互時被 Iterator 和 IntoInterator 特性高的有些蒙圈。最后是靠著 Python 和 Rust 相互對比,弄清迭代器與可迭代對象的區別后才感覺自己真正弄懂了迭代這一重要特性。

延申閱讀

《流暢的Python》 - 第14章,可迭代的對象、迭代器和生成器

《Rust 編程之道》 6.3 迭代器

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373744.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373744.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373744.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WebSphere Application Server性能調整工具包

IBM已發布了WebSphere Application Server性能調整工具包 &#xff0c;該工具包具有從Eclipse工作區*監視多個 WebSphere Application Server的功能。 該工具使用WAS Performance Monitoring統計信息來獲取并繪制圖表&#xff0c;以指示服務器的運行狀況。 *請注意&#xff0c;…

CentOS 配置防火墻操作實例(啟、停、開、閉端口)

CentOS 配置防火墻操作實例&#xff08;啟、停、開、閉端口&#xff09;&#xff1a; 注&#xff1a;防火墻的基本操作命令&#xff1a; 查詢防火墻狀態: [rootlocalhost ~]# service iptables status<回車> 停止防火墻: [rootlocalhost ~]# service iptables stop &…

linux常用命令-壓縮解壓命令

壓縮解壓命令 目錄 1. 壓縮解壓命令&#xff1a;gzip 2. 壓縮解壓命令&#xff1a;gunzip 3. 壓縮解壓命令&#xff1a;tar 4. 壓縮解壓命令&#xff1a;zip 5. 壓縮解壓命令&#xff1a;unzip 6. 壓縮解壓命令&#xff1a;bzip2 7. 壓縮解壓命令&#xff1a;bunzip2 1. 壓縮解…

達夢數據庫查詢數據庫所有表名_達夢數據庫的一些實用小SQL

1)當前數據庫中的模式名&#xff1a;select distinct object_name TABLE_SCHEMA from all_objects where object_type SCH;2)查出各模式對應的用戶&#xff1a;selectSCH_OBJ.NAME ,SCH_OBJ.ID ,SCH_OBJ.CRTDATE,USER_OBJ.NAMEfrom(select NAME, ID, PID, CRTDATE from …

設置Java EE 6開發環境

本教程簡要說明了如何設置典型的環境來開發基于Java EE 6的應用程序。 除了可以正常工作的Windows XP客戶端具有足夠的CPU能力和內存外&#xff0c;本教程沒有其他先決條件。 在教程中&#xff0c;我們將需要安裝以下組件&#xff1a; Java 6 JDK更新26 用于Java EE開發人員的…

css cursor url用法格式詳解

css cursor url用法格式&#xff1a;css:{cursor:url(圖標路徑),auto;} //IE,FF,chrome瀏覽器都可以 實例代碼&#xff1a;html{cursor: url("http://ued.taobao.com/blog/wp-content/themes/taobaoued/images/cursor.ico"),auto;} 解析&#xff1a;前面的url是自定義…

iostext添加點擊事件_iOS開發小技巧 - label中的文字添加點擊事件

Label中的文字添加點擊事件以前老師講過類似的功能,自己懶得回頭看了,找了很多第三方的,感覺這個小巧便利,作者只是擴展了分類,實現起來代碼也少.先來個效果圖自己的項目,直接上代碼- (void)setTopicModel:(CYQTopicModel *)topicModel{_topicModel topicModel;NSArray *likeA…

ubantu下安裝Nginx

Nginx 概述 Nginx ("engine x") 是一個高性能的 HTTP 和 反向代理 服務器&#xff0c;也是一個 IMAP/POP3/SMTP 代理服務器。 Nginx 是由 Igor Sysoev 為俄羅斯訪問量第二的 Rambler.ru 站點開發的&#xff0c;第一個公開版本0.1.0發布于2004年10月4日。其將源代碼…

Hadoop中的問題–何時無法交付?

Hadoop是很棒的軟件。 它不是原始的&#xff0c;但肯定不能消除它的榮耀。 它建立在并行處理的基礎上&#xff0c;這個概念已經存在了數十年。 Hadoop雖然從概念上來說并不是獨創性的&#xff0c;但它顯示了自由開放的力量&#xff08;就像在啤酒中一樣&#xff01;&#xff09…

創建 dblink

目的&#xff1a;oracle中跨數據庫查詢 兩臺數據庫服務器db_A(本地)和db_B(遠程192.168.1.100)&#xff0c;db_A下用戶user_a 需要訪問到db_B下user_b的數據解決&#xff1a;查詢得知使用dblink(即database link 數據庫鏈)實現過程&#xff1a;1、確定用戶user_a有沒有創…

C#靜態常量和動態常量的區別

C#擁有兩種不同的常量&#xff1a;靜態常量(compile-time constants)和動態常量(runtime constants)。它們有不同的特性&#xff0c;錯誤的使用不僅會損失效率&#xff0c;還可能造成錯誤。相比之下&#xff0c;靜態常量在速度上會稍稍快一些&#xff0c;但是靈活性卻比動態常…

spring的鉤子_高級java開發必須掌握的Spring接口——SmartLifecycle

有些場景我們需要在Spring 所有的bean 完成初始化后緊接著執行一些任務或者啟動需要的異步服務。 常見有幾種解決方案j2ee 注解 啟動前PostConstruct 銷毀前PreDestroy 基于j2ee 規范springboot 的 org.springframework.boot.CommandLineRunner springboot 特性前面我已經介紹過…

Java:對Java SE 6和Java SE 7的客戶端和桌面部分的改進!

Java 6和Java 7中的客戶端改進 了解有關Java SE 6和Java SE 7的客戶端和桌面部分的改進&#xff0c;包括新的applet插件&#xff0c;Java Deployment Toolkit&#xff0c;成形和半透明的窗口&#xff0c;重量級-輕量級混合以及Java Web Start。 介紹 自2006年12月發布Java平臺…

辨異 —— 行星 vs 恒星

star&#xff1a;恒星&#xff0c;planet&#xff1a;行星&#xff1b;1. 恒星 恒星是指宇宙中靠核聚變產生的能量而自身能發熱發光的星體&#xff08;比如太陽&#xff09;。過去天文學家以為恒星的位置是永恒不變的&#xff0c;以此為名。但事實上&#xff0c;恒星也會按照一…

軟件公司職責分配

崗位&#xff1a;項目經理 主要職責&#xff1a;1、 計劃&#xff1a;a)項目范圍、項目質量、項目時間、項目成本的確認。b)項目過程/活動的標準化、規范化。c)根據項目范圍、質量、時間與成本的綜合因素的考慮&#xff0c;進行項目的總體規劃與階段計劃。d)各項計劃得到上級領…

大型網站架構系列:負載均衡詳解(4)

本文是負載均衡詳解的第四篇&#xff0c;主要介紹了LVS的三種請求轉發模式和八種負載均衡算法&#xff0c;以及Haproxy的特點和負載均衡算法。具體參考文章&#xff0c;詳見最后的鏈接。 三、LVS負載均衡 LVS是一個開源的軟件&#xff0c;由畢業于國防科技大學的章文嵩博士于19…

關于JavaFX的最常見問題

上周&#xff0c;我在斯德哥爾摩的Jfokus 2012上做了一個關于JavaFX的演講&#xff0c;當時我意識到每次活動都會問三個問題。 似乎有一個普遍的興趣&#xff0c;所以我嘗試在這篇文章中回答他們&#xff08;盡可能的說實話&#xff09;&#xff1a; iPad或其他移動設備上的Jav…

python中面向對象空間時間_python基礎學習Day15 面向對象、類名稱空間、對象名稱空間 (2)...

一、類先看一段代碼&#xff1a;classPerson:animal 高級動物walk_way 直立行走 # 靜態屬性&#xff0c;靜態變量&#xff0c;靜態字段language 語言def __init__(self,name,age,work): # 函數 動態屬性&#xff0c;方法#print(self)self.name nameself.ageageself.workworkdef…

Linux GRUB 引導Win 7 ---- error: invalid EFI file path

最近新買了個固態硬盤&#xff0c;先裝了個Win 7系統&#xff0c;現在裝的系統和以前裝系統唯一的區別是引導不是以前的MBR&#xff0c;而是最新看似是個趨勢的GPTUEFI方式。 win 7 裝完啦&#xff0c;還是和以往的一樣裝 Ubantu (Ubantu 12.04)&#xff0c;ubantu 引導磁盤扇…

其他位不變,具體位的賦值操作

GPIOC (GPIOC & 0xf0) | (Content[s_Index] & 0x0f);        //低四位賦值GPIOB (GPIOB & 0xc3) | ((Content[s_Index]>>2) & 0x3c);     //中間四位賦值 具體某一位置1或取反&#xff1a; 正確寫法&#xff1a; 置1:GPIOC | (1<<i…