当前位置:   article > 正文

python—— __iter__和__next__

__iter__

python里面有很多的以__(注意:这里是两个下划线)开始和结尾的函数,利用它们可以完成很多复杂的逻辑代码,而且提高了代码的简洁性,下面以迭代器的概念引入相关内容。

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()。

字符串,列表或元组对象都可用于创建迭代器:

>>> list=[1,2,3,4]
>>> it = iter(list)    # 创建迭代器对象
>>> print (next(it))   # 输出迭代器的下一个元素
1
>>> print (next(it))
2
>>>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

迭代器对象可以使用常规for语句进行遍历:

>>>list=[1,2,3,4]
>>>it = iter(list)    # 创建迭代器对象
>>>for x in it:
>>>    print (x, end=" ")  
1 2 3 4    
  • 1
  • 2
  • 3
  • 4
  • 5

it = iter(list) 这一句可以不写,因为python内部会对for语句后面的对象进行如下的变换:

for x in iter(list)
  • 1

创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。

iter() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。

next() 方法(Python 2 里是 next())会返回下一个迭代器对象。我们就可以通过next函数访问这个对象的下一个元素了,并且在你不想继续有迭代的情况下抛出一个StopIteration的异常(for语句会捕获这个异常,并且自动结束for)

创建一个返回数字的迭代器,初始值为 1,逐步递增 1:

class A(object):
	def __init__(self,end):
		self.start = 0
		self.end = end
	def __iter__(self):
		return self
	def __next__(self):
		if self.start < self.end:
		   self.start + = 1
		   return self.start
		else:
		   raise StopIteration 

a = A(5)

for i in a:
    print(i)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出:

1
2
3
4
5
  • 1
  • 2
  • 3
  • 4
  • 5

把for语句换成next函数也可以:

class A(object):
	def __init__(self,end):
		self.start = 0
		self.end = end
	def __iter__(self):
		return self
	def __next__(self):
		if self.start < self.end:
		   self.start + = 1
		   return self.start
		else:
		   raise StopIteration 

a = A(5)

print(next(a))   
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a)) # 其实到这里已经完成了,我们在运行一次查看异常
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

输出:

0
1
2
3
4
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-51-d93a95b5b2c9> in <module>()
     21 print(next(a))
     22 print(next(a))
---> 23 print(next(a)) # 其实到这里已经完成了,我们在运行一次查看异常

<ipython-input-51-d93a95b5b2c9> in __next__(self)
     13             return ret
     14         else:
---> 15             raise StopIteration
     16 
     17 a = MyRange(5)

StopIteration: 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

可以看见一个很明显的好处是,每次产生的数据,是产生一个用一个,什么意思呢,比如我要遍历[0, 1, 2, 3…]一直到10亿,如果使用列表的方式,那么是会全部载入内存的,但是如果使用迭代器,可以看见,当用到了(也就是在调用了next)才会产生对应的数字,这样就可以节约内存了,这是一种懒惰的加载方式。

注:

(1)构造函数

构造函数也被称为构造器,当创建对象的时候第一个被自动调用的函数,系统默认提供了一个无参的构造函数 per = Person()

  • 语法:

    def  __ init__(self,arg1,arg2,...):
    	函数体
    
    • 1
    • 2
  • 说明:

     当你没有在类中定义构造函数的话,系统默认提供了一个无参的构造函数
     arg1,arg2,...可以自己定义,但是,一般情况下,构造函数的形参列表和成员变量有关
     构造函数的特点:创建对象;给对象的成员变量赋值
    
    • 1
    • 2
    • 3
  • 构造函数和成员函数之间的区别:

     成员函数的函数名可以自定义,但是,构造函数的函数名是固定的__init__
     成员函数需要被手动调用,但是,构造函数在创建对象的过程中是自动被调用的
     对于同一个对象而言,成员函数可以被调用多次,但是,构造函数只能被调用一次
    
    • 1
    • 2
    • 3

(2)class、instance、object、method、function

class :类
instance:实例
object:对象,和instance等同
method:类中的方法
function:普通函数,类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self

——————
python里面的self,是谁啊?

(3)self:

self代表类的实例,而非类

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()

T = Test()
T.prt()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出

<__main__.Test object at 0x000001EDDE8BFFD0>
<class '__main__.Test'>

<__main__.Test object at 0x000001EDDE7C45F8>
<class '__main__.Test'>
  • 1
  • 2
  • 3
  • 4
  • 5

从执行结果可以很明显的看出,self 指代引用类的method的实例,代表当前实例的地址,可以通过self.xxx来改变实例的属性
而 self.class 则指向类。

self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:

class Test:
    def prt(food):
        print(food)
        print(food.__class__)
 
t = Test()
t.prt()

T = Test()
T.prt()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出:

<__main__.Test object at 0x000001EDDE7A69E8>
<class '__main__.Test'>
<__main__.Test object at 0x000001EDDE8BFFD0>
<class '__main__.Test'>
  • 1
  • 2
  • 3
  • 4

(4)Iterable、Iterator

Iterable: 有迭代能力的对象,一个类,实现了__iter__,那么就认为它有迭代能力,通常此函数必须返回一个实现了__next__的对象,如果自己实现了,你可以返回self,当然这个返回值不是必须的;
Iterator: 迭代器(当然也是Iterable),同时实现了 iter__和__next__的对象,缺少任何一个都不算是Iterator,比如下面例子中,A()可以是一个Iterable,但是A()和B()都不能算是和Iterator,因为A只实现了__iter(),而B只实现了__next__()。

class B(object):
    def __next__(self):
        raise StopIteration

class A(object):
    def __iter__(self):
        return B()

a = A()
b = B()

print(next(a))
print(next(b))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出:

Traceback (most recent call last):
  File "d:/objectdetection/models/yolov3/Rebar_Detection-master/test.py", line 12, in <module>
    print(next(a))
TypeError: 'A' object is not an iterator

Traceback (most recent call last):
  File "d:/objectdetection/models/yolov3/Rebar_Detection-master/test.py", line 12, in <module>
    print(next(b))
  File "d:/objectdetection/models/yolov3/Rebar_Detection-master/test.py", line 3, in __next__
    raise StopIteration
StopIteration
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们可以使用collections里面的类型来进行验证:

class B(object):
    def __next__(self):
        raise StopIteration

class A(object):
    def __iter__(self):
        return B()


from collections.abc import *

a = A()
b = B()
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))

print(isinstance(b, Iterable))
print(isinstance(b, Iterator))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
True
False
False
False
  • 1
  • 2
  • 3
  • 4

可以对B类进行修改:

class B(object):
    def __next__(self):
        raise StopIteration

    def __iter__(self):
        return None

class A(object):
    def __iter__(self):
        return B()


from collections.abc import *

a = A()
b = B()
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))

print(isinstance(b, Iterable))
print(isinstance(b, Iterator))

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
True
False
True
True
  • 1
  • 2
  • 3
  • 4

(5)生成器

生成器是一个可以快速创建迭代器的工具

  • 简单的生成器:生成器表达式

我们可以用列表推导(生成式)来初始化一个列表:

list5 = [x for x in range(5)]
print(list5)   #output:[0, 1, 2, 3, 4]
  • 1
  • 2

我们用类似的方式来生成一个生成器,只不过我们这次将上面的[ ]换成( ):

gen = (x for x in range(5))
print(gen) 
#output: <generator object <genexpr> at 0x0000000000AA20F8>
  • 1
  • 2
  • 3

看到上面print(gen) 并不是直接输出结果,而是告诉我们这是一个生成器。那么我们要怎么调用这个gen呢。
有两种方式:

第一种:

for item in gen:
    print(item)
#output:
0
1
2
3
4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第二种:

print(next(gen))#output:0
print(next(gen))#output:1
print(next(gen))#output:2
print(next(gen))#output:3
print(next(gen))#output:4
print(next(gen))#output:Traceback (most recent call last):StopIteration
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

好了。现在可以考虑下背后的原理是什么了。
从第一个用for的调用方式我们可以知道生成器是可迭代的。更准确的说法是他就是个迭代器。
我们可以验证一下:

from collections import Iterable, Iterator
print(isinstance(gen, Iterable))#output:True
print(isinstance(gen, Iterator))#output:True
  • 1
  • 2
  • 3

str,list,tuple,dict,set这些都是可迭代的,就是可用for来访问里面的每一个元素。但他们并不是迭代器。

  • 在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象。


import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()
        
from collections import Iterable, Iterator
print(isinstance(f, Iterable))#output:True
print(isinstance(f, Iterator))#output:True
#output
0 1 1 2 3 5 8 13 21 34 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • send和throw函数

注意上面说的yield表达式的返回值,我们以前使用的都是yield start这种格式,其实yield是有返回值的,默认情况下都是None,我们修改一下上面的MyRange

def MyRange(end):
    start = 0
    while start < end:
        x = yield start  # 这里增加了获取返回值
        print(x)  # 打印出来
        start += 1

m = MyRange(5)
print(next(m))
print(next(m))
#输出
0
None
1

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

yield执行顺序上面已经说明了,这里打印了一个None,就是yield的返回值,那么说了这么多,就是为了说明send函数的参数,会作为yield的返回值的。

def MyRange(end):
    start = 0
    while start < end:
        x = yield start
        print(x)
        start += 1


m = MyRange(5)
print(next(m))
print(m.send(10))

#输出
0
10
1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我们在来看看throw这个函数,它是让yield产生一个异常,接收三个参数,异常类型,异常值,trackback对象,其中后面两个可选,同send一样,会返回下一个值。

如果这个异常不在生成器函数里面进行处理,会抛出到调用者
  • 1
def MyRange(end):
    start = 0
    while start < end:
        try:
            x = yield start
        except Exception as e:
            print(e)
        start += 1


m = MyRange(5)
print(next(m))
print(m.throw(Exception, 'Some Exception'))
#输出
0
Some Exception
1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

总结:
生成迭代器的方法有很多种。
1、同时实现了 __iter__和__next__的对象,缺少任何一个都不算是Iterator。
2、生成器是生成迭代器的工具。

————
【Python魔术方法】迭代器(iter__和__next)
Python3 迭代器与生成器
对Python生成器的理解

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/145525
推荐阅读
相关标签
  

闽ICP备14008679号