1. 程式人生 > >3. 物件迭代與反迭代相關問題與解決技巧

3. 物件迭代與反迭代相關問題與解決技巧

一. 如何實現可迭代物件和迭代器物件

實際案列

某軟體要求, 從網路抓取各個城市氣溫資訊, 並依次顯示:
    北京: 15-20
    天津: 17-22
    長春: 12-18
    ...

如果一次抓取所有城市氣溫再顯示, 顯示第一個城市氣溫時, 有很高的延時,
並且浪費儲存空間, 我們期望以“用時訪問”的策略, 並且能把所有城市氣溫
封裝到一個物件裡, 可用for語句進行迭代。  如何解決?

可迭代物件和迭代器原理

from collections import Iterable, Iterator
l = [1,2,3,4,5]


isinstance(l, Iterable)  # True  列表是可迭代物件
issubclass(list, Iterable)  # True  列表是Iterable的子類
issubclass(str, Iterable)  # True  字串是Iterable的子類
issubclass(dict, Iterable)  # True  字典是Iterable的子類


it = iter(l)  #  呼叫了__iter__方法    生成Iterator 迭代器

next(it)  #  next 可以對迭代器進行迭代   直到丟擲 StopIteration異常  無法next


list(it)  # 迭代器傳給列表, 可以將剩下沒next的  變成列表


for x in it:   # 迭代器也是一個可迭代物件
    print(x)
    
isinstance(it, Iterable)  # True   迭代器也是一個可迭代物件

it.__iter__() is it   # True       或者iter(it)   都會返回它自身

解決方法

Step1: 實現一個迭代器物件WeatherIterator, __next__方法  每次返回一個城市的氣溫


Step2: 實現一個可迭代物件WeatherIterable, __iter__方法 返回一個WeatherIterator物件

from collections import Iterable, Iterator
import requests


class WeatherIterator(Iterator):
    def __init__(self, caties):
        self.caties = caties
        self.index = 0

    def __next__(self):
        if self.index == len(self.caties):
            raise StopIteration
        city = self.caties[self.index]
        self.index += 1
        return self.get_weather(city)

    def get_weather(self, city):
        url = 'http://wthrcdn.etouch.cn/weather_mini?city=' + city  # 獲取相應城市氣溫
        r = requests.get(url)
        data = r.json()['data']['forecast'][0]
        return city, data['high'], data['low']


class WeatherIterable(Iterable):
    def __init__(self, cities):
        self.cities = cities

    def __iter__(self):
        return WeatherIterator(self.cities)


def show(w):
    for x in w:
        print(x)


w = WeatherIterable(['北京', '上海', '廣州'] * 10)
show(w)

程式碼的注意點

如果不要WeatherIterable, 直接用WeatherIterator, 也可以。
但是 如果 show 結束  再次呼叫show  就什麼都沒有了。
因為Iterator 迭代器是一次性的, 但迭代器物件Iterable不是一次性的

二. 如何使用生成器函式實現可迭代物件

實際案列

實現一個可迭代物件的類, 它能迭代出給定範圍內所有素數:
    
    pn = PrimeNumbers(1, 30)
    for k in pn:
        print k
    
    輸出結果:
    2 3 5 7 11 13 17 19 23 29


用一節  中的 方法也能實現, 但用yield生成器 會更簡單

解決方案

將該類的__iter__方法實現生成器函式, 每次yield返回一個素數


from collections import Iterable

class PrimeNumbers(Iterable):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __iter__(self):
        for k in range(self.a, self.b + 1):
            if self.is_prime(k):
                yield k

    def is_prime(self, k):
        return False if k < 2 else all(map(lambda x: k % x, range(2, k)))      #  k%x  為0  必然不是素數

pn = PrimeNumbers(1, 30)
for n in pn:
    print(n)

三. 如何進行反向迭代以及如何實現反向迭代

實際案例

實現一個連續浮點數發生器FloatRange(和range類似), 根據給定範圍(start, end) 和步進值(step)
產生一些連續浮點數, 如迭代FloatRange(3.0, 4.0, 0.2) 可產生序列:

正向: 3.0->3.2->3.4->3.6->3.8->4.0
反向: 4.0->3.8->3.6->3.4->3.2->3.0

實現反向迭代

from decimal import Decimal   #  專門處理10進值浮點數

class FloatRange:
    def __init__(self, a, b, step):
     #  有時候會發生3.400000000000000004 這樣的誤差持續放大的問題,  
     #  使用Decimal解決, 產生10進值的浮點數  就沒有誤差。  在需要精準資料時, 常用
        self.a = Decimal(str(a))   
        self.b = Decimal(str(b))
        self.step = Decimal(str(step))

    def __iter__(self):
        t = self.a
        while t <= self.b:
            yield float(t)  #  使用者需要的是python的float, 需要轉換。另外的注意點:decimal物件是無法與float進行運算的
            t += self.step

    def __reversed__(self):  #  實現反向迭代  reversed
        t = self.b
        while t >= self.a:
            yield float(t)
            t -= self.step

fr = FloatRange(3.0, 4.0, 0.2)
for x in fr:
    print(x)
print('-' * 20)
for x in reversed(fr):
    print(x)

四. 如何對可迭代物件做切片操作

實際案列

有某個文字檔案, 我們想讀取其中某範圍的內容如100~300 行之間的內容, python中檔案是可迭代物件,
我們是否可以使用類似列表切片的方式得到一個100~300行檔案內容的生成器?

f = open('/var/log/dpkg.log.1')
for line in f[100:300]:       #  可以嗎?   答案是 不可以

原理解析


l = list(range(10))

l[2:8]   # 即 l.__getitem__(slice(2,8))    slice  是切片函式, 對應[2:8]
l[2:8: 2]   # 即 l.__getitem__(slice(2,8, 2))
l[3]    # 即 l.__getitem__(3)



f = open('/var/log/dkpg.log.1')

# f.readlines可以  使用切片, 但會把真個檔案都讀入記憶體, 不推薦

解決方案

使用 itertools.islice, 它能返回一個迭代物件切片的生成器


from itertools import islice
f = open('/var/log/dkpg.log.1')

for line in islice(f, 100-1, 300, 2):  #  引數: 可迭代物件   開始行(預設從0開始)  結束行  步進
    print(line)

自己實現簡化版的islice


def my_islice(iterable, start, end, step=1):
    tmp = 0
    for i, x in enumerate(iterable):
        if i >= end:
            break
        if i >= start:
            if tmp == 0:
                tmp =step
            yield x
        tmp -= 1

五. 如何在一個for語句中迭代多個可迭代物件

實際案例

情況1(並行): 某班學生期末考試成績, 語文, 數學, 英語 分別儲存在3個列表中, 同時迭代三個列表, 計算每個學生的總分


情況2(序列): 某年級有4個班, 每次考試每班英語成績分別儲存在4個列表中, 依次迭代每個列表, 統計全學年成績高於90分的人數

解決方案

- 並行:  使用zip函式  將多個可迭代物件 合併成一個

from random import randint

chinese = [randint(60, 100) for _ in range(20)]
math = [randint(60, 100) for _ in range(20)]
english = [randint(60, 100) for _ in range(20)]


t = []
for s1, s2, s3 in zip(chinese, math, english):  # zip得到的元組  是傳入 的可迭代物件 中最少的   
    t.append(s1+s2+s3)   # 得到三科總分
    


- 序列:使用標準庫中的itertools.chain, 它能將多個可迭代物件連線

class1 = [randint(60, 100) for _ in range(20)]
class2 = [randint(60, 100) for _ in range(20)]
class3 = [randint(60, 100) for _ in range(23)]
class4 = [randint(60, 100) for _ in range(25)]

from itertools import chain

len([for x in chain(class1, class2, class3, class4) if x > 90])   #  得到分數大於90的 學生人數