odoo 关于self的迭代器(2)迭代器进阶

在python中,一切都是对象,什么样的对象可以迭代。或者说这个对象可以迭代是不是因为他包含了什么特殊的属性?

回想下上一篇,for in循环做了什么,好像是4步,其实只有2步。

第一步,判断是否可迭代,iterable通过iter转换为iterator的过程。

第二步执行next到结束(如果我们类里包含了关于next结束的判断,就不会报错了,所以肯定是没有第三步的)。

那么类里面"必然"包含2个部分,iter和next。

class Fib:
    '''iterator that yields numbers in the Fibonacci sequence'''

    def __init__(self, max):
        self.max = max

    def __iter__(self):
        self.a = 0
        self.b = 1
        return self

    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

这是从官网抄的一个例子,很漂亮。为了方便我就直接写for循环调用了,不太理解的跟上文连起来看。

fib = Fib(1000)
for n in fib:
	print(n, end=' ')

print()
print(iter(fib))
print(fib)

结果:首先打印出来的是菲波那切数列,这个无疑问了,最后的两行是一样的都是self。

分析:

我们先进行了fib的实例化,然后调用了for循环,因为没有报错,说明fib是可调用的(iterable),然后我们对fib进行了iter方法,生成了iterator。后面的打印告诉我们,我们之前的fib(iterable)和生成的iterator是相同的,因为我们类中__iter__返回的是self本身,所以是相同的。最后我们进入了循环即调用__next__。

iterable和iterator的官方定义

iterable

能够一次返回自身的成员的对象是可迭代的。比如一切的序列类型(list,str,tuple)还有一些非序列类型dict,file object,和一切定义了__iter__方法或者__getitem__方法的类,他们执行了序列的语法。

可迭代对象能被用在for循环中和在其他的一些需要的地方比如zip(), map()。当一个可迭代对象被作为一个参数传递给iter()的时候,他返回了这个对象的迭代器(iterator)。这个迭代器擅长逐个取出对象中的值。当使用可迭代对象的时候,通常不需要调用iter(),或处理迭代器对象,for声明会自动为你处理的,在循环期间创建一个临时的未命名变量保存在迭代器中。详细请看iterator,sequence,generator

iterator

一个数据流对象,重复调用迭代器的__next__()方法(或者是next()),从数据流中逐次的返回数据。如果没有更多可用的数据,将抛出一个StopIteration。在这个点上,以后怎么调用next(),都会再次抛出StopIteration。迭代器需要有__iter__()方法,这个方法用来返回这个迭代器对象本身,所以每一个迭代器(iterator)都是可迭代的(iterable),同样,他可以在大多数情况下被当作可迭代的对象使用。我们应该注意尝试多次迭代传递这样的代码。一个容器(container)对象(比如list)在你将他传递给iter()或者用for循环的时候产生了一种新的迭代器。用迭代器尝试他,只会返回上次迭代器传递中使用的相同的已经耗尽的迭代器对象,使其看起来像个空容器。我理解蓝字的意义就是迭代器被iter()或者for调用生成的还是迭代器,生成的这个和原来的是一样的,所以不断地用iter()调用一个迭代器是没意义的。(英文不好,错了请指正)

总结一下iterable和iterator,就2点

1.序列类型,定义了__iter__和__getitem__方法的类是可迭代的。

2.调用迭代器用next()或者__next__()

接下来主要讲讲这两个问题

什么是序列

首先他是一个迭代器,通过__getitem__方法使用整数索引进行有效的元素访问。并定义__len__方法,__len__方法返回序列的长度。

说明索引用的是__getitem__方法。

那么我们可以用__iter__和__getitem__来定义可迭代对象了?

class A:
    def __getitem__(self, index):
        if index >= 10:
            raise IndexError
        return index * 111
for i in A():
	print(i)


class B:
    def __iter__(self):
        yield 10
        yield 20
        yield 30
for i in B():
	print(i)

从上面的例子可以看出,这2个例子都没有next(),__getitem__调用next实际调用了他本身,不会考虑__next__,而iter()调用的是yield。

如果合在一起,会怎么样?

class C:
    def __getitem__(self, index):
        if index >= 10:
            raise IndexError
        return index * 111
    def __iter__(self):
        yield 10
        yield 20
        yield 30

for i in C():
	print(i)

第三个例子说明同时有__iter__和__getitem__的时候,不考虑__getitem__。

既然可以没有__next__,我们在反过来考虑下,可不可以没有__iter__?

class D():
    def __init__(self,data=1):
        self.data = data

    def __next__(self):
        if self.data > 5:
            raise StopIteration
        else:
            self.data+=1
            return self.data

t = D(3)   
# for i in t:
# 	print(i)
for i in range(3):
    print(t.__next__())

貌似也可以,不过我们发现,这里t并不是一个迭代器,注释部分报错了,后面部分成功是因为range(3)是可迭代的,作用是读取个数,有点类似我上一节说的读取个数的循环。t只有__next__方法。

所以,迭代器是必须有__iter__的,也可以说,__iter__规定了他可以是可迭代的,但是可以没有__next__。

同样,我们会发现有些类只有__iter__,没有__next__,是不能for循环的,个人理解,他作为一个可迭代的,但是没有序列这种格式的语法。如果有,比如yield这样是可以的。

如何检测迭代器?

1.用collections模块

这个模块提供了一些基础的类,我们可以通过是否是实例来判断

import collections

if isinstance(e, collections.Iterable):
    # e is iterable

这个不能判断__getitem__

2.用生成器

try:
   _ = (e for e in my_object)
except TypeError:
   print my_object, 'is not iterable'

其实你可以理解为直接用了for循环

3.用iter()

try:
    some_object_iterator = iter(some_object)
except TypeError as te:
    print some_object, 'is not iterable'

yield

这个算是附加吧,我们也可以看到,一般情况下什么样的函数是可以迭代的。

下面我们用一个例子,来说明yield:

def foo():
    print("begin")
    for i in range(3):
        print("before yield", i)
        yield i
        print("after yield", i)
    print("end")
print(foo().__iter__)

f = foo()
next(f)
# for i in f:
# 	print('here', i)

这个例子推荐在python命令行一行一行输入。因为这样你可以更好的发现问题。

这个函数他的__iter__已经定义过了,为了避免你有以为,我提前调用了。

首先执行f =foo()

这个时候begin是没有出现的

如果你是下面的代码:

def foo():
	print('begin')
f = foo()

会有begin的

其次你输入next(f)

begin
before yield 0

我们发现输出在yield之前的内容

再次输入next(f)

after yield 0
before yield 1

我们发现确实是在走循环,但是yield的内容去哪了?

这次输入print(next(f))

after yield 1
before yield 2
2

我们看到yield的值作为返回值传出来了。

yield的工作模式应该已经清楚了。


猜你喜欢

转载自blog.csdn.net/wwx890208/article/details/80803248
今日推荐