# -*- 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