迭代,迭代器,生成器

# -*- coding:utf-8 -*-
# 迭代---------
# 如果給定一個list或tuple,我們可以通過for循環來遍歷這個list或tuple,這種遍歷稱爲迭代
# 在python中,迭代通過for...in完成,而很多語言比如c或者Java,迭代通過下標完成,如Java代碼

'''
for (i = 0,i<list.length;i++){
n = list[i]
}
可以看出,python的for循環抽象成都要高於Java的for循環,因爲python的for循環不僅僅可以用在list
或者tuple上,還可作用在其他迭代器對象上,list這種數據類型雖然有下標,但很多其他數據類型是沒
有下標的,但是只要是可迭代的對象,無論有無下標,都可以迭代,比如dict就可以
'''
d = dict(a = 1,b = 2,c = 3);
print d;  #{'a': 1, 'c': 3, 'b': 2}
for key in d:
    print key;  # a c b dict不是按照list順序排列,所以,迭代出的結果順序可能不一樣。

#默認情況下 dict迭代的是key,如果要迭代value,可以用forvalue in d.itervalues(),如果要同時
# 迭代key和value,可以用for k,v in d.iteritems()

# 字符串也是可迭代對象,因此,也可以作用於for循環

for c in 'abcd':
    print c; #a b c d

#所以當使用for循環時,只要作用於一個可迭代對象,for循環就可以正常運行,而我們不太關心該對象是list
# 還是其他數據類型

#判斷一個對象是否可迭代用collections模塊的Iterable
from collections import Iterable
print isinstance('xyz',Iterable); #True str
print isinstance([1,2,3],Iterable); #True list
print isinstance(123,Iterable); # False 整數
print isinstance((1,2,3),Iterable); #True 元組

# 如果要對list實現類似Java那樣下標循環,python內置的enumerate函數可以吧一個list變成索引元素對
# 這樣就可以for循環中同時迭代索引和元素本身
for i,value in enumerate (['a','b']):
    print i,value;
'''
0 a
1 b    
'''

# 上面for循環裏,同時引用了兩個變量,在python中很常見,如下面代碼
for x,y in [(1,1),(2,3),(4,5)]:
    print x,y;
'''
1 1
2 3
4 5
'''



#-------------------------------------------------------------------------
#--------包裝-----------------
# 用functools.partial()可以將函數包裝成更簡潔的版本

from functools import partial
def test(a,b,c):
    print a,b,c;

f = partial(test,b = 2,c = 3); #爲後續參數提供默認命名值
print f(7); # 7 2 3

f = partial(test,7,c = 3) #爲前面位置參數和後面的命名參數提供默認值
print f(5); # 7 5 3

#python會按下面的規則合併參數
from functools import partial
def partial(func,*d_args,**d_kwargs):
    def wrap(*args,**kwargs):
        new_args = d_args + args;  #合併位置參數,partial提供的默認值優先
        nwe_kwargs = d_kwargs.copy(); #合併命名參數,partial提供的會被覆蓋
        new_kwargs.update(kwargs);
        print new_args;
        print nwe_kwargs;
        return fuc(*new_args,**new_kwargs);
    return wrap
    f = partial(1,'ddd',b = 2)
    wrap('aa',c = 1)
#暫未輸出



#-------------------------------------------------------------------
#---列表生成式
'''
列表生成式即list comparehensions,是python內置的非常簡單卻很強大的可以用來創建list的生成式
舉例生成list[1,2,3,4,5,6,7]可以用list(xrange(1,8))
'''
print list(range(1,8)); #[1, 2, 3, 4, 5, 6, 7]

#生成[1*1,2*2,...7*7]
#循環
l = [];
for x in xrange(1,8):
    l.append(x*x);
print l; #[1, 4, 9, 16, 25, 36, 49]

#列表生成式
print list([x * x for x in xrange(1,8)]);#[1, 4, 9, 16, 25, 36, 49]
print [x * x for x in xrange(1,8)] #[1, 4, 9, 16, 25, 36, 49]
# 寫列表生成式時,要把生成的元素x*x放在前面,後面跟for循環,就可以把list創建出來

# for後可以加IF判斷,這樣就可以篩選出偶數或奇數的平方
print [x * x for x in xrange(1,8) if x % 2 ==0];#[4, 16, 36]
print [x * x for x in xrange(1,8) if x % 2 != 0]; #[1, 9, 25, 49]

# 用兩層循環生成全排列
print [m + n for m in 'ABC' for n in '1','2','3']
# ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']

# 列出當前目錄下的所有文件和目錄名
import os #導入os模塊
print [s for s in os.listdir('.')];
'''
'['aaa.py', 'demo1.12.25.py', 'demo2.12.26.py', 'demo3.12.26.py', 'demo4.12.27.py', 
 'demo5.12.27.py', 'demo6.12.28.py', 'demo7.01.2.py', 'Include', 'Lib', 'Scripts', 'tcl']
'''

# for循環可以同時使用兩個甚至多個變量,比如dict的items()可以同事迭代key和value
d = dict(a = 'A',b = 'B',c = 'c');
print d; #{'a': 'A', 'c': 'c', 'b': 'B'}
for k,v in d.items():
    print k,':',v
'''
a : A
c : c
b : B
'''

#列表生成式也可以使用兩個變量來生成list;
d = dict(x = 'X',y = 'Y',z = 'Z');
print [k + '=' + v for k, v in d.items()] #['y=Y', 'x=X', 'z=Z']

#將一個list中的所有字符串變小寫
L = ['HHHe','WWo','APPLE'];
print [s.lower() for s in L]; #['hhhe', 'wwo', 'apple']



#---------------------------------------------------------------------------
#---生成器--
'''
通過列表生成式,我們可以直接創建一個表,但是受到內存限制,列表容量肯定是有限的。而且,創建一個包含
100萬個元素的列表,不僅佔用很大的存儲空間,如果我們只訪問前面的幾個元素,那後面絕大多數元素佔用的
空間都浪費了
所以,如果列表元素可以按照某種算法推算出來,那我們就可以在循環的過程中不斷推算出後續的元素,這樣就
不必創建完整的list,從而節省大量的空間,在python中,這種一邊循環,一邊計算的機制交生成器 generator
創建一個generater,有很多方法,第一種簡單的方法,只要把一個列表生成式的[]改成()
'''
L = [x * x for x in xrange(1,5)]; #[1, 4, 9, 16]
print L;
g = (x * x for x in xrange(1,5));
print g ;  #<generator object <genexpr> at 0x000000000499D438>

'''
創建L和g的區別僅在於自外層的[]和(),L是list,g是generator,我們可以直接打印出list的每一個元素,
而g是一個generator。可以直接打印出list的每一個元素,用next()函數獲得generator的下一個返回值,
'''
print next(g); #1
print next(g); #4
print next(g); #9

#可以用for循環,generator可迭代
g = (x * x for x in xrange(1,5));
for n in g:
    print (n);  # 1,4,9,16
'''
所以當創建了一個generator後,基本上不會用next()而是用for來迭代他,並且不需要關心StopIteration
錯誤。   
generator非常強大,如果推算的算法比較複雜,用類似的列表生成的for循環無法實現的時候,還可以用函數實現
如,著名的斐波拉契數列fibonacci,除第一個和第二個數外,任意一個數都可以由前兩個數相加得到1,1,2,3,5,8...
用列表生成式寫不出來,但是,用函數很容易
'''

def fib(x):
    n,a,b = 0,0,1;
    while n<x:
        print (b);
        a,b = b,a+b;
        n = n+1;
    return 'done';
fib(5); # 1,1,2,3,5

'''
a,b = b,a + b 相當於
t = (b,a + b); #t是一個tuple
a = t[0];
b = t[1];
但不必顯式寫出臨時變量t就可以賦值
上面的函數可以輸出斐波納挈數列的前n個數

仔細觀察,fib函數實際上是定義了斐波那契數列的推算規則,可以從第一個元素開始,推算後續任意的元素,這種
邏輯很類似generator

也就是說,上面的函數和generator僅一部之遙,要把fib函數變成generator,只把print(b)改爲yield(b)
'''
def fib(x):
    n, a, b = 0, 0, 1
    while n < x:
        yield b
        a, b = b, a + b
        n = n + 1
print fib(6) #<generator object fib at 0x000000000503A5A0>
# 這是定義generator的另一個方法,如果一個函數定義中包含yield關鍵字,那麼這個函數就不是普通函數,
# 而是generator

'''
這裏難理解的就是generator和函數的執行流程不一樣,函數是順序執行,遇到return語句或者最後一行函數
就返回,而變成generator函數,在每次調用next的時候執行,遇到yield語句返回,再次執行是從上次返回的
yield處繼續執行.
'''

# 舉一個簡單的例子,定義一個generator,依次返回數字1.3.5
def odd():
    print('step 1');
    yield 1;
    print('step 2');
    yield 3;
    print ('step 3');
    yield 5;
o = odd()
print next(o);
'''
step 1
1
'''
print next(o);
'''
step 2
3
'''
print next(o);
'''
step 3
5
可以看到,odd不是普通函數,而是generator,在執行過程中,遇到yield就中斷,執行3次後沒有yield可執行
了就會報錯
回到fib例子,在循環過程中不斷調用yield,就會不斷中斷,當然要給循環設置一個條件來退出循環,不然就會產生
無線數列
同樣的,把函數改成generator後,基本上不會用next來獲取下一個返回值,而是用for迭代
'''

for n in fib(5):
    print (n); # 1 1 2 3 5

'''
但是用for循環調用generator時,發現拿不到generator的return語句的返回值,必須捕獲stopIteration
錯誤,返回值包含在stopIteration的value中
'''
g = fib(5);
while True:
    try:
        x = next(g);
        print('g:'),x;
    except StopIteration as e:
        print('Generator return value:',e);
        break;
'''
g: 1
g: 1
g: 2
g: 3
g: 5
('Generator return value:', StopIteration())
'''



#----------------------------------------------------------
#----迭代器
'''
可以直接作用於for循環的數據類型有,一類爲集合數據類型(list tuple dict str..
一類是generator,包括生成器和帶yield的generator function
這些能直接作用於for循環的對象統稱爲可迭代對象Iterable,可以用isinstance()判斷是否可迭代
生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,知道最後拋出StopIteration
可以被next調用並不斷返回下一個值的對象叫迭代器:Iterator
生成器都是Iterator 對象,但list dict str 雖然是Iterable,卻不是Iterator
用isinstance()判斷一個對象是否是Iterator對象
把list dict str 等iterable(可迭代對象)變成Iterator(迭代器)用ITER()函數

'''
from collections import Iterator;
print isinstance((x for x in range(5)),Iterator); #True
print isinstance([],Iterator); #False
print isinstance({},Iterator); #False
print isinstance('abcd',Iterator); #False

print isinstance(iter([]),Iterator); #True
print isinstance(iter('azx'),Iterator); #True

'''
爲什麼list dict str等數據類型不是Iterator
因爲python的Iterator對象表示一個數據流,Iterator對象可以被next函數調用並不斷返回下一個數據,直到
沒數據拋出StopIterateration錯誤,可以把這個數據流看成是一個有序的序列,但我們卻不知道序列的長度,只能
通過不斷調用next函數實現按需計算下一個數據,所以Iterator得計算是有惰性得,只有在需要返回下一個數據
時他纔會計算
Iterator甚至可表示一個無線大得數據流,例如全體自然數,然而使用list是永遠不可能存儲全體自然數的
凡是可用於for循環的對象都是Iterator類型
凡是可作用於next函數的對象都是Iterator類型,他們表示一個惰性計算的序列
集合數據類型list dict str等是Iterable但不是Iterator,可通過iter()函數獲得一個Iterator對象
python的for循環本質就是不斷調用next函數實現
'''




#---------------------------------------------------------------------------
#--補充
# -----------------------------------------------------------------------
#-----迭代器
# 迭代器協議,僅需要__iter__()和next()兩個方法,前者返回迭代器對象,後者依次返回數值,直到引發
# stopIteration異常結束
# 最簡單的做法是用內置函數iter(),它返回常用類型的迭代器包裝對象,問題是,序列類型已經可以被for處理
# 爲什麼還要這麼做?

class Data(object):
   def __init__(self):
      self._data = []
   def add(self, x):
       self._data.append(x)
   def data(self):
       return iter(self._data)
d = Data();
d.add(1)
d.add(2)
d.add(3)
for x in d.data():
    print x;    #1  2  3
# 返回迭代器對象self._data列表,可避免對象狀態被外部修改,或許你會嘗試返回tuple,但這需要複製整個
# 列表,浪費更多的內存

'''
iter()很方便,但無法讓迭代中途停止,這需自己動手實現迭代器對象。在設計原則上,通常會將迭代器從數據
對象中分離出去,因爲迭代器需要維持狀態,而且可能有多個迭代器在同時操控數據,這些不該成爲數據對象的
負擔,無端提升了複雜度
'''
class Data(object):
    def __init__(self,*args):
        self._data = list(args);
    def __iter__(self):
        return  DataIter(self);
class DataIter(object):
    def __init__(self,data):
        self._index = 0;
        self._data = data._data;
    def next(self):
        if self._index >= len(self._data):
            raise StopIteration();
        d = self._data[self._index];
        self._index += 1;
        return d;
d = Data(1,2);
for x in d:
    print x;  # 1 2
#Data 僅僅是數據容器,只需__iter__返回迭代器對象,而由DataIter生成器提供next方法

# 除了for循環,迭代器也可以直接用next()操控
s = Data ('a','b','c');
it = iter(s);
print it  #<__main__.DataIter object at 0x00000000057FF780>
print next(it); #a
print next(it); #b
print next(it); #c
# print next(it); # raise StopIteration();
print '不用next,用for'
for  i in s :
    print i; #a b c




print '---------------------------------------------------------------------'
#--生成器
# 基於索引實現的迭代器有些醜陋,更合理的做法是用yield返回實現了迭代器協議Generator對象
class Data(object):
    def __init__(self,*args):
        self._data = list(args);
    def __iter__(self):
        for x in self._data:
            yield x;
d = Data(1,2,3);
for x in d:
    print x; # 1,2,3


# 編譯器會將包含yield的方法或函數重新打包,使其返回Generator對象,這樣一來,就無需費力氣維護額外的
# 迭代器類型了
print '這是用next'
print d.__iter__()#<generator object __iter__ at 0x0000000004BACC60>
print iter(d).next();  #1
print iter(d).next();  #1   ???bug 不懂
print iter(d).next();  #1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章