이 글은 Python의 반복자와 반복자 슬라이스에 대해 자세히 소개합니다. 필요한 친구들이 참고할 수 있기를 바랍니다.
Python 슬라이싱에 관한 처음 두 기사에서 우리는 기본 사용법, 고급 사용법, 슬라이싱에 대한 오해, 사용자 정의 객체가 슬라이싱 사용법을 구현하는 방법을 배웠습니다(관련 내용은 기사 끝 부분 참조). 링크) ). 이번 글은 슬라이싱 시리즈의 세 번째 글이며, 주요 내용은 이터레이터 슬라이싱이다.
Iterator는 Python의 고유한 고급 기능이며, 슬라이싱도 고급 기능입니다. 이 둘을 결합하면 어떤 결과가 나올까요?
우선, 명확히 해야 할 몇 가지 기본 개념이 있습니다: 반복, 반복 가능한 객체 및 반복자.
반복
은 컨테이너 유형 객체(예: 문자열, 목록, 사전 등)를 순회하는 방법입니다. 예를 들어 문자열 "abc"를 반복한다고 합니다. 수단은 모든 문자를 왼쪽에서 오른쪽으로 하나씩 꺼내는 과정입니다. (PS: 중국어로 iteration이라는 단어는 반복되는 주기와 레이어별 진행을 의미하지만, Python에서는 이 단어를 迭代
是一种遍历容器类型对象(例如字符串、列表、字典等等)的方式,例如,我们说迭代一个字符串“abc”,指的就是从左往右依次地、逐个地取出它的全部字符的过程。(PS:汉语中迭代一词有循环反复、层层递进的意思,但 Python 中此词要理解成单向水平线性 的,如果你不熟悉它,我建议直接将其理解为遍历。)
那么,怎么写出迭代操作的指令呢?最通用的书写语法就是 for 循环。
# for循环实现迭代过程 for char in "abc": print(char, end=" ") # 输出结果:a b c
for 循环可以实现迭代的过程,但是,并非所有对象都可以用于 for 循环,例如,上例中若将字符串“abc”换成任意整型数字,则会报错: 'int' object is not iterable .
这句报错中的单词“iterable”指的是“可迭代的”,即 int 类型不是可迭代的。而字符串(string)类型是可迭代的,同样地,列表、元组、字典等类型,都是可迭代的。
那怎么判断一个对象是否可迭代呢?为什么它们是可迭代的呢?怎么让一个对象可迭代呢?
要使一个对象可迭代,就要实现可迭代协议,即需要实现__iter__()
One-way 수평 선형
# 方法1:dir()查看__iter__ dir(2) # 没有,略 dir("abc") # 有,略 # 方法2:isinstance()判断 import collections isinstance(2, collections.Iterable) # False isinstance("abc", collections.Iterable) # True # 方法3:hasattr()判断 hasattr(2,"__iter__") # False hasattr("abc","__iter__") # True # 方法4:用iter()查看是否报错 iter(2) # 报错:'int' object is not iterable iter("abc") # <str_iterator> ### PS:判断是否可迭代,还可以查看是否实现__getitem__,为方便描述,本文从略。</str_iterator>
객체를 반복 가능하게 만들려면 반복 가능 프로토콜을 구현해야 합니다. 즉, __iter__()
매직 메서드를 구현해야 합니다. 이 매직 메소드를 구현하는 객체는 Iterable 객체입니다. 그럼 객체가 이 메서드를 구현하는지 여부를 어떻게 판단할 수 있을까요? 위의 for 루프 외에도 네 가지 다른 메서드가 있다는 것을 알고 있습니다:
ob1 = "abc" ob2 = iter("abc") ob3 = iter("abc") # ob1它遍历 for i in ob1: print(i, end = " ") # a b c for i in ob1: print(i, end = " ") # a b c # ob1自遍历 ob1.__next__() # 报错: 'str' object has no attribute '__next__' # ob2它遍历 for i in ob2: print(i, end = " ") # a b c for i in ob2: print(i, end = " ") # 无输出 # ob2自遍历 ob2.__next__() # 报错:StopIteration # ob3自遍历 ob3.__next__() # a ob3.__next__() # b ob3.__next__() # c ob3.__next__() # 报错:StopIteration
이러한 메서드 중 가장 주목할만한 메서드는 Python의 내장 메서드인 iter() 메서드와 해당 함수입니다.
반복 가능한 객체를 반복자로 변환입니다. 이 문장은 두 가지 의미로 해석될 수 있습니다. (1) 반복 가능한 객체와 반복자는 서로 다른 것입니다. (2) 반복 가능한 객체는 반복자가 될 수 있습니다.
사실 반복자는 반복 가능한 객체여야 하지만 반복 가능한 객체가 반드시 반복자는 아닙니다. 둘 사이의 차이는 얼마나 큽니까?
위 그림의 파란색 원에 표시된 것처럼 일반 반복 가능 객체와 반복자 간의 가장 중요한 차이점은 다음과 같습니다. 소위 "동일하다"는 것은 둘 다 반복 가능하다는 것을 의미합니다(__iter__). 소위 "두 개의 서로 다르다"는 것은 반복 가능한 객체가 반복자로 변환된 후 일부 속성(__getitem__)을 잃게 됨을 의미합니다. 일부 속성(__next__)을 늘립니다.추가된 속성 __next__을 살펴보세요. 이것이 반복자가 반복자인 이유에 대한 핵심입니다. 실제로 우리는 __iter__ 메서드와 __next__ 메서드를 모두 구현하는 객체를 반복자로 정의합니다. .
이 추가 속성을 사용하면 반복 가능한 객체는 외부 for 루프 구문을 사용하지 않고도 자체 반복/순회 프로세스를 구현할 수 있습니다. 나는 이 두 가지 순회 프로세스를 설명하기 위해 두 가지 개념을 고안했습니다. (PS: 이해의 편의를 위해 여기서는 순회라고 부르지만 실제로는 반복이라고 부를 수도 있습니다.) 순회는 외부 문법을 통해 구현된 순회를 의미하고, 자체 순회는 구현된 순회를 의미합니다. 자신만의 방법을 통해서. #🎜🎜##🎜🎜#이 두 가지 개념의 도움으로 반복 가능한 객체는 "그것에 의해 탐색"될 수 있는 객체이고 반복자는 "자체 탐색"될 수도 있는 객체라고 말합니다. 이 기초. #🎜🎜#hi = "欢迎关注公众号:Python猫" it = iter(hi) # 普通切片 hi[-7:] # Python猫 # 反例:迭代器切片 it[-7:] # 报错:'str_iterator' object is not subscriptable
前面提到了“一同两不同”,最后的不同是,普通可迭代对象在转化成迭代器的过程中会丢失一些属性,其中关键的属性是 __getitem__ 。在《Python进阶:自定义对象实现切片功能》中,我曾介绍了这个魔术方法,并用它实现了自定义对象的切片特性。
那么问题来了:为什么迭代器不继承这个属性呢?
首先,迭代器使用的是消耗型的遍历,这意味着它充满不确定性,即其长度与索引键值对是动态衰减的,所以很难 get 到它的 item ,也就不再需要 __getitem__ 属性了。其次,若强行给迭代器加上这个属性,这并不合理,正所谓强扭的瓜不甜......
由此,新的问题来了:既然会丢失这么重要的属性(还包括其它未标识的属性),为什么还要使用迭代器呢?
这个问题的答案在于,迭代器拥有不可替代的强大的有用的功能,使得 Python 要如此设计它。限于篇幅,此处不再展开,后续我会专门填坑此话题。
还没完,死缠烂打的问题来了:能否令迭代器拥有这个属性呢,即令迭代器继续支持切片呢?
hi = "欢迎关注公众号:Python猫" it = iter(hi) # 普通切片 hi[-7:] # Python猫 # 反例:迭代器切片 it[-7:] # 报错:'str_iterator' object is not subscriptable
迭代器因为缺少__getitem__ ,因此不能使用普通的切片语法。想要实现切片,无非两种思路:一是自己造轮子,写实现的逻辑;二是找到封装好的轮子。
Python 的 itertools 模块就是我们要找的轮子,用它提供的方法可轻松实现迭代器切片。
import itertools # 例1:简易迭代器 s = iter("123456789") for x in itertools.islice(s, 2, 6): print(x, end = " ") # 输出:3 4 5 6 for x in itertools.islice(s, 2, 6): print(x, end = " ") # 输出:9 # 例2:斐波那契数列迭代器 class Fib(): def __init__(self): self.a, self.b = 1, 1 def __iter__(self): while True: yield self.a self.a, self.b = self.b, self.a + self.b f = iter(Fib()) for x in itertools.islice(f, 2, 6): print(x, end = " ") # 输出:2 3 5 8 for x in itertools.islice(f, 2, 6): print(x, end = " ") # 输出:34 55 89 144
itertools 模块的 islice() 方法将迭代器与切片完美结合,终于回答了前面的问题。然而,迭代器切片跟普通切片相比,前者有很多局限性。首先,这个方法不是“纯函数”(纯函数需遵守“相同输入得到相同输出”的原则,之前在《来自Kenneth Reitz大神的建议:避免不必要的面向对象编程》提到过);其次,它只支持正向切片,且不支持负数索引,这都是由迭代器的损耗性所决定的。
那么,我不禁要问:itertools 模块的切片方法用了什么实现逻辑呢?下方是官网提供的源码:
def islice(iterable, *args): # islice('ABCDEFG', 2) --> A B # islice('ABCDEFG', 2, 4) --> C D # islice('ABCDEFG', 2, None) --> C D E F G # islice('ABCDEFG', 0, None, 2) --> A C E G s = slice(*args) # 索引区间是[0,sys.maxsize],默认步长是1 start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1 it = iter(range(start, stop, step)) try: nexti = next(it) except StopIteration: # Consume *iterable* up to the *start* position. for i, element in zip(range(start), iterable): pass return try: for i, element in enumerate(iterable): if i == nexti: yield element nexti = next(it) except StopIteration: # Consume to *stop*. for i, element in zip(range(i + 1, stop), iterable): pass
islice() 方法的索引方向是受限的,但它也提供了一种可能性:即允许你对一个无穷的(在系统支持范围内)迭代器进行切片的能力。这是迭代器切片最具想象力的用途场景。
除此之外,迭代器切片还有一个很实在的应用场景:读取文件对象中给定行数范围的数据。
在《给Python学习者的文件读写指南(含基础与进阶,建议收藏)》里,我介绍了从文件中读取内容的几种方法:readline() 比较鸡肋,不咋用;read() 适合读取内容较少的情况,或者是需要一次性处理全部内容的情况;而 readlines() 用的较多,比较灵活,每次迭代读取内容,既减少内存压力,又方便逐行对数据处理。
虽然 readlines() 有迭代读取的优势,但它是从头到尾逐行读取,若文件有几千行,而我们只想要读取少数特定行(例如第1000-1009行),那它还是效率太低了。考虑到文件对象天然就是迭代器 ,我们可以使用迭代器切片先行截取,然后再处理,如此效率将大大地提升。
# test.txt 文件内容 ''' 猫 Python猫 python is a cat. this is the end. ''' from itertools import islice with open('test.txt','r',encoding='utf-8') as f: print(hasattr(f, "__next__")) # 判断是否迭代器 content = islice(f, 2, 4) for line in content: print(line.strip()) ### 输出结果: True python is a cat. this is the end.
위 내용은 Python의 반복자와 반복자 슬라이싱에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!