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.mzph.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…