Python迭代器的作用是提供一種
遍歷數(shù)據(jù)集合
的方式。它是一個可以被迭代
的對象,可以使用迭代器的方法來逐個訪問集合中的元素,而不需要事先知道集合
的大小。
在深度學習的Dataset
和Dataloader
中,就是通過迭代器
實現(xiàn)的,因此迭代器是一個非常重要的概念和工具
迭代器具有以下幾個重要的特點:
-
節(jié)省內(nèi)存
:迭代器一次只返回一個元素,不需要一次性將整個集合加載到內(nèi)存中
,這樣可以節(jié)省內(nèi)存空間,特別是在處理大型數(shù)據(jù)集合時非常有用。 -
惰性計算
:迭代器在需要時才會計算下一個元素
,而不是一次性計算所有的元素。這種惰性計算的方式可以在處理大量數(shù)據(jù)時提高效率。 -
可逆迭代
:迭代器可以反向遍歷集合,而不需要額外的復制和存儲。 -
支持并行處理
:迭代器可以同時遍歷多個集合,實現(xiàn)并行處理。
總之,迭代器提供了一種靈活、高效和節(jié)省內(nèi)存的方式來遍歷數(shù)據(jù)集合,是Python中非常重要的概念和工具。
1. 迭代器與可迭代對象(Iterable)
1.1 可迭代對象(Iterable)
表示該對象可迭代
, 它的類中需要定義__iter__
方法,只要是實現(xiàn)了__iter__方法的類就是可迭代對象
。
from collections.abc import Iterable, Iterator
class A(object):
def __init__(self):
self.a = [1, 2, 3]
def __iter__(self):
# 此處返回啥無所謂
return self.a
cls_a = A()
# True
print(isinstance(cls_a, Iterable))
-
可迭代對象,必須具備
__iter__
這個特殊函數(shù),并且返回
一個可迭代對象。可以通過isinstance(cls_a, Iterable)
可以判斷是否是可迭代對象 -
如果一個Iterable,僅僅定義了
__iter__
方法,是沒有特別大的用途,因為依然無法迭代,實際上 Iterable 僅僅是提供了一種抽象規(guī)范接口
1.2 迭代器( Iterator)
-
迭代器Iterator一定是可迭代對象Iterable
,但反過來,可迭代對象不一定是迭代器
,因為迭代器只是可迭代對象的一種表示形式。 - 實現(xiàn)了
__next__
和__iter__
方法的類才能稱為迭代器
就可以被 for 循環(huán)遍歷數(shù)據(jù)。
因此,通過自定義實現(xiàn)迭代器Iterator
,必須具備__next__
和__iter__
兩個方法,如下案例所示:
class A(object):
def __init__(self):
self.index = -1
self.a = [1, 2, 3]
# 必須要返回一個實現(xiàn)了 __next__ 方法的對象,否則后面無法 for 遍歷
# 因為本類自身實現(xiàn)了 __next__,所以通常都是返回 self 對象即可
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index < len(self.a):
return self.a[self.index]
else:
# 拋異常,for 內(nèi)部會自動捕獲,表示迭代完成
raise StopIteration("遍歷完了")
cls_a = A()
print(isinstance(cls_a, Iterable)) # True
print(isinstance(cls_a, Iterator)) # True 從這里可以看出來同時具有__iter__和__next__的類,是Iterator
print(isinstance(iter(cls_a), Iterator)) # True 這里加不加iter()都一樣,因為這個類里面的iter也是直接返回自身(self)
#另外補充一點這個a和上面類里面的a是不一樣的;這里的用i(任意字母都可以)也能
for a in cls_a:
print(a)
# 打印 1 2 3
-可以看到,通過實現(xiàn)__iter__
, 和__next__
這兩個特殊方法,實現(xiàn)了迭代器A。
2. 自定義一個可迭代器
2.1 實現(xiàn)迭代器
在Python中從頭開始構建迭代器很容易。我們只需要實現(xiàn)
這些方法__iter__()
和__next__()
。
-
__iter__()
方法需要返回迭代器對象
, 最簡單直接返回self
,也可以返回新的可迭代對象。如果需要,可以執(zhí)行一些初始化
。 -
__next__()
方法必須返回序列中的下一項
。在到達終點時,以及在隨后的調(diào)用中,它必須引發(fā)StopIteration
這里,我們展示了一個示例,通過定義一個迭代器
,手動實現(xiàn)python的range
方法:
class Range:
def __init__(self,start,stop,step):
self.start = start
self.stop = stop
self.step = step
def __iter__(self):
self.value = self.start
return self
def __next__(self):
# 1. 每執(zhí)行一次next,需要返回一個值
# 2. 如果沒有下一個值了,需要通過StopIteration 反饋異常
if self.value < self.stop:
old_value = self.value
self.value = self.value +self.step
return old_value
else:
raise StopIteration()
for i in Range(0,5,1):
print(i)
輸出結果
我們知道python 的range方法 有三個參數(shù):start_value, stop_value和step,因此我們也定義這3個參數(shù)。
- 首先定義類的
__init__
方法 - 然后實現(xiàn)
__iter__
方法,并返回可迭代對象,這里返回了本身(slef)。其中在__iter__
方法中,初始化了返回值self.value
(__iter__
方法中,如果需要,可以執(zhí)行一些初始化
) - 最后通過
__next__
方法,定義每一次迭代輸出的值。當?shù)炅?,沒有下一個值,通過raise StopIteration()
, 反饋錯誤。
在for循環(huán)中,最后反饋的raise StopIteration()
erro,會被for循環(huán)
處理掉,因此我們看不到報錯的提醒。
2.2 for 遍歷迭代器的過程
通過單步調(diào)試,可以觀察到如下執(zhí)行順序:文章來源:http://www.zghlxwxcb.cn/news/detail-812869.html
- (1) 首先調(diào)用
__init__
方法,對成員變量進行初始化 - (2) 緊接著,進入
__iter__
,獲得一個迭代器對象self - (3) 最后進入
__next__
方法,執(zhí)行循環(huán)迭代過程
,每迭代一次返回相應的迭代結果。最后迭代會執(zhí)行raise StopIteration()
語句,此時程序結束
(異常被for處理,所以沒有顯示出來)
可以看到所謂for循環(huán),本質上是就是一次次的執(zhí)行__next__
方法的過程。for循環(huán)可等價如下代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-812869.html
r = Range(0,5,1) # __init__ 構造對象
iteration = iter(r) # 執(zhí)行__iter__,獲得迭代器對象
#iteration = r.__iter__()
next(iteration) # 執(zhí)行__next__
#iteration.__next__()
next(iteration) # 執(zhí)行__next__
next(iteration) # 執(zhí)行__next__
next(iteration) # 執(zhí)行__next__
next(iteration) # 執(zhí)行__next__
try:
next(iteration)
except StopIteration as e:
pass
- 最后一次會報
StopIteration
異常,因此通過try except
進行處理。 - 注:`
-
iter(r)
就是r.__iter__()
實現(xiàn)的 -
next(iteration)
就是通過iteration.__next__()
實現(xiàn)的
-
3. yolov8 Dataset實現(xiàn)案例
class LoadImages:
# YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4`
def __init__(self, path, img_size=640, stride=32, auto=True, transforms=None, vid_stride=1):
"""Initialize instance variables and check for valid input."""
if isinstance(path, str) and Path(path).suffix == '.txt': # *.txt file with img/vid/dir on each line
path = Path(path).read_text().rsplit()
files = []
for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
p = str(Path(p).resolve())
if '*' in p:
files.extend(sorted(glob.glob(p, recursive=True))) # glob
elif os.path.isdir(p):
files.extend(sorted(glob.glob(os.path.join(p, '*.*')))) # dir
elif os.path.isfile(p):
files.append(p) # files
else:
raise FileNotFoundError(f'{p} does not exist')
images = [x for x in files if x.split('.')[-1].lower() in IMG_FORMATS]
videos = [x for x in files if x.split('.')[-1].lower() in VID_FORMATS]
ni, nv = len(images), len(videos)
self.img_size = img_size
self.stride = stride
self.files = images + videos
self.nf = ni + nv # number of files
self.video_flag = [False] * ni + [True] * nv
self.mode = 'image'
self.auto = auto
self.transforms = transforms # optional
self.vid_stride = vid_stride # video frame-rate stride
if any(videos):
self._new_video(videos[0]) # new video
else:
self.cap = None
assert self.nf > 0, f'No images or videos found in {p}. ' \
f'Supported formats are:\nimages: {IMG_FORMATS}\nvideos: {VID_FORMATS}'
def __iter__(self):
"""Returns an iterator object for iterating over images or videos found in a directory."""
self.count = 0
return self
def __next__(self):
"""Iterator's next item, performs transformation on image and returns path, transformed image, original image, capture and size."""
if self.count == self.nf:
raise StopIteration
path = self.files[self.count]
if self.video_flag[self.count]:
# Read video
self.mode = 'video'
for _ in range(self.vid_stride):
self.cap.grab()
ret_val, im0 = self.cap.retrieve()
while not ret_val:
self.count += 1
self.cap.release()
if self.count == self.nf: # last video
raise StopIteration
path = self.files[self.count]
self._new_video(path)
ret_val, im0 = self.cap.read()
self.frame += 1
# im0 = self._cv2_rotate(im0) # for use if cv2 autorotation is False
s = f'video {self.count + 1}/{self.nf} ({self.frame}/{self.frames}) {path}: '
else:
# Read image
self.count += 1
im0 = cv2.imread(path) # BGR
assert im0 is not None, f'Image Not Found {path}'
s = f'image {self.count}/{self.nf} {path}: '
if self.transforms:
im = self.transforms(im0) # transforms
else:
im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0] # padded resize
im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
im = np.ascontiguousarray(im) # contiguous
return path, im, im0, self.cap, s
dataset = LoadImages(source, imgsz=imgsz, vid_stride=vid_stride)
dataloader = build_dataloader(dataset, batch_size, workers, shuffle, rank)
- 可以看到Dataset,也是通過一個迭代器實現(xiàn),這樣做的
好處
就是:需要時才會返回處理好的數(shù)據(jù)
,而不需要一次性將整個集合加載到內(nèi)存中
,這樣可以節(jié)省內(nèi)存空間,也提高了數(shù)據(jù)處理的效率。
到了這里,關于python高級(1): 迭代器詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!