首页新闻动态正文

Python Rust 迭代器对比【黑马python培训】

更新时间:2019年07月26日 10时49分47秒 来源:黑马程序员论坛

迭代是数据处理的基石,而 Python 中所有集合都可以迭代,这是 Python 让使用者感到非常方便的特征之一。

我们通常都会在一些平台看到,例如这个平台(www.smpeizi.com):

下面是一些在 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)) # 结果为1print(next(data_iter1)) # 结果为2data_iter2 = iter(data)print(next(data_iter2)) # 结果为1print(next(data_iter1)) # 预期为3,但得到2

如果把 current 属性变为列表,每次调用 iter 增加一个元素表示新的迭代器当前位置呢?但又会导致 __next__ 变得非常复杂,因为它必须找到不同迭代器对应当前位置,这样才能保证正
确的迭代行为。为什么我们的迭代实现如此复杂呢?根本原因在于 __iter__ 总是返回自身,换言之,调用 iter 的迭代器都是一样,这其实破坏了 每次调用 iter 返回新的迭代器 这一设计。

解决难题办法很简单,遵循设计,把可迭代对象和迭代器拆开。

下面我们再来看这个平台(www.pzzs168.com)

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,相当于构造函数// 这个方法不是必须的,但可以让我更加方便// 地使用 Counterimpl Counter {    fn new() -> Counter {        Counter { count: 0 }    }}// 实现 Iterator 特性impl Iterator for Counter {    // 确定迭代器的返回值类型    type Item = usize;    // 只有 next() 是必须实现的方法    // Option<usize> 也可以写成 Option<Self::Item>    fn next(&mut self) -> Option<usize> {        // 增加计数        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<Self::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,这个特性可以把某些东西变成一个迭代器。

最后结合这个平台(www.idiancai.com)的IntoInterator 声明如下:

pub trait IntoIteratorwhere    <Self::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<i32>,}struct DataIterator {    current: usize,    data: Vec<i32>,}impl DataIterator {    fn new(values: Vec<i32>) -> DataIterator {        DataIterator {            current: 0,            data: values        }    }}impl Iterator for DataIterator {    type Item = i32;    fn next(&mut self) -> Option<i32> {        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 相互对比,弄清迭代器与可迭代对象的区别后才感觉自己真正弄懂了迭代这一重要特性。

推荐了解热门学科

java培训 Python人工智能 Web前端培训 PHP培训
区块链培训 影视制作培训 C++培训 产品经理培训
UI设计培训 新媒体培训 产品经理培训 Linux运维
大数据培训 智能机器人软件开发




传智播客是一家致力于培养高素质软件开发人才的科技公司“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。

传智播客从未停止思考

传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”

中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。

何为中高级程序员课程?

传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。




黑马程序员热门视频教程【点击播放】

Python入门教程完整版(懂中文就能学会) 零起点打开Java世界的大门
C++| 匠心之作 从0到1入门学编程 PHP|零基础入门开发者编程核心技术
Web前端入门教程_Web前端html+css+JavaScript 软件测试入门到精通


在线咨询 我要报名
和我们在线交谈!