参照了这位大神的博客然后总结了一下:https://blog.csdn.net/rainharder/article/details/73368770
何为迭代
迭代有两个特征:
1.重复进行某一特征
2.下一次操作依赖于上次操作的结果
见如下例子:
for i in range(5):
print("Hello") # 非迭代,只是重复打印 Hello
print(i) # 迭代,不止打印i的值,并且每次打印i都加1
何为可迭代对象
顾名思义,即可被迭代的对象
见如下例子:
在python中导入collections库中的Iterable类,在这个例子里,下面的isinstance函数是用来判断传入的数据类型对象是否为Iterable,即是否为可迭代对象。
from collections import Iterable
f = open('a.txt')
i = 1
s = "1234"
d = {'abc':1}
t = (1,2,345)
print(isinstance(f,Iterable)) #判断文件对象是否为可迭代对象
print(isinstance(i,Iterable)) #判断整型是否为可迭代对象
print(isinstance(s,Iterable)) #判断字符串对象是否为可迭代对象
print(isinstance(d,Iterable)) #判断字典对象是否为可迭代对象
print(isinstance(t,Iterable)) #判断元组对象是否为可迭代对象
>>>
True
False
True
True
True
可知,除了整型,文件类型、字符串类型、字典型、元组类型都是可迭代对象。
那isinstance这个函数内部又是如何判断传入的对象是否为可迭代对象的呢?
因为,每个可迭代对象里都有一个__iter__()方法,正是因为这个方法,才使得这个基本数据类型变为可迭代
现在,我们再用一个函数hasattr()来好好看看:
f = open('a.txt')
i = 1
s = "1234"
d = {'abc':1}
t = (1,2,345)
#hasattr(obj,string) 判断对象中是否有string方法
print(hasattr(f,'__iter__')) #判断文件对象是否为可迭代对象
print(hasattr(i,'__iter__')) #判断整型是否为可迭代对象
print(hasattr(s,'__iter__')) #判断字符串对象是否为可迭代对象
print(hasattr(d,'__iter__')) #判断字典对象是否为可迭代对象
print(hasattr(t,'__iter__')) #判断元组对象是否为可迭代对象
>>>True
False
True
True
True
可知,除了整型对象,其余的数据对象都有__iter__()方法
#没有 __iter__()方法
class A:
def __init__(self):
pass
B = A()
print(isinstance(B,Iterable))
>>>False
#有 __iter__()方法
class A:
def __init__(self):
pass
def __iter__(self):
pass
B = A()
print(isinstance(B,Iterable))
>>>True
何为迭代器对象(即迭代器)
实现了迭代器协议的对象
(那什么是迭代器协议?我们待会再讲)
首先,我们看看什么是容器:
容器是一个把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取。比如在python中,属于容器类型的有: list、dict、str、tuple等。
但容器并不具有提供数据的功能,那我们是怎么获取数据的呢?是迭代器赋予了容器这种功能。迭代器对象与可迭代对象区别就在于迭代器对象多了一种__next__()方法。
我们来看下面的例子,我们同样用hasattr()函数来判断:
from collections import Iterable
f = open('a.txt')
i = 1
s = "1234"
d = {'abc':1}
t = (1,2,345)
#hasattr(obj,string) 判断对象中是否有string方法
print(hasattr(f,'__next__')) #判断文件对象是否为迭代器对象
print(hasattr(i,'__next__')) #判断整型是否为迭代器对象
print(hasattr(s,'__next__')) #判断字符串对象是否为迭代器对象
print(hasattr(d,'__next__')) #判断字典对象是否为迭代器对象
print(hasattr(t,'__next__')) #判断元组对象是否为迭代器对象
>>>True
False
False
False
False
可知,除了整型对象都是可迭代对象,但是只有文件时迭代器(对象)
迭代器是如何工作的呢
我们先看下面这个例子:
lst = [1, 2, 3, 4, 5]
for i in lst:
print(i)
>>> 1 2 3 4 5
它内部的运行机制:
1.调用__iter__()方法,将可迭代对象(这里时列表lst)变成一个迭代器
2.这个迭代器再调用其__next__()方法,返回取到的第一个值,这个元素就被赋值给了i
3.打印输出
4.抛出异常,循环结束
下面我们来模拟循环的每一步并输出:
lst = [1,2,3,4,5]
#将lst变为迭代器
item = lst.__iter__()
#迭代器调用next方法,并且返回取出的元素
print(item.__next__())
print(item.__next__())
print(item.__next__())
print(item.__next__())
print(item.__next__())
#报StopIteration错误
print(item.__next__())
>>>1
2
3
4
5
在最后一次调用item.next()的时候,它会报错,
为什么呢?因为此时已经迭代到最后一个值、没有返回值可以得到了,这说明循环该结束了。
上面的例子还可以写成:
lst = [1,2,3,4,5]
#等价于 lst.__iter__() 人为显示调用
for i in iter(lst):
print(i)
#解释器隐式调用
for i in lst:
print(i)
#两种输出均为1,2,3,4,5
所以,我们可以这么认为,一般for循环in后面的对象至少必须是一个可迭代对象。
何为迭代器协议
对象需要提供__next__()方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代。
如何自建迭代器
1.具有__iter__()方法,iter()方法会返回一个迭代器(Iterator)
2.返回的迭代器具有__next__()方法,每次迭代都会返回下一个对象,当没有下一个对象元素时,则会引发StopIteration类型错误。
该过程我们可以使用iter()方法调用对象的__iter__()方法获取一个迭代器,使用next()方法来调用迭代器的__next__()方法。我们看下面的例子:
def forIn(iterable,func):
iterator = iter(iterable)
try:
while True:
p = next(iterator)
func(p)
except StopIteration:
pass
forIn([1,5,7,10],print)
>>>1
5
7
10
何为生成器
生成器是一类特殊的迭代器。区别在于:自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法和__next__方法,但它没有这个两个函数),即通过自己特殊的方式。
- 作用?只在需要的时候才产生一个生成器结果对象,不是立即产生结果,
Python有两种方式提供生成器
1.通过生成器表达式:通俗讲,就是把一对中括号 [] 变换成一对小括号 ()
见如下例子:
lst = [1,2,3,4,5]
#生成器generator,类似list,但是把[]改成()
gen = (a for a in lst)
for i in gen:
print(i)
>>>1
2
3
4
5
我们还可以在shell里做如下对比:
L返回的是列表类型,但G返回的是生成器对象,你只有循环调用next()函数才能访问到里面的每个元素,如果你只调用一次,则它只返回一个元素。
L = [x*2 for x in range(5)]
L
>>>[0,2,4,6,8]
G = (x*2 for x in range(5))
G
>>><generator object <genexpr> at 0x00000230C53857D8>
for i in range(5):
next(G)
>>>0
2
4
6
8
2.通过生成器函数,函数内部有yield关键字。
现在我们来看看yield 与 return的关系
相同点:都是返回程序中执行结果
区别:yield返回执行结果并不中断程序执行,yield语句一次返回一个结果,在每个结果中间,呈现函数挂起状态,以便下次从它离开的地方继续执行。
而return在返回执行结果的同时中断程序执行
我们现在上例子:
def return_test(n):
for i in range(n):
return 2*n
for i in range(return_test(3)):
print(i)
>>>0
1
2
3
4
5
当传入3时,由于return返回的是2*n即6,所以从0开始输出直到5。
我们再看yield的例子:
def yield_test(n):
for i in range(n):
yield 2*i
for i in yield_test(3):
print(i)
>>>0
2
4
该函数返回的是2*i,第一次返回0,函数挂起,再依次返回2、4.
细心的同学可以发现,为什么return例子打印循环那里要加一个range,但是yield例子里却不用加?这说明,return 返回的就是它本身的数据类型,所以我们要通过range将他包装成一个可迭代对象,但是yield返回的是一个迭代器,自然它是一个可迭代对象啦。
装饰器
即高阶函数 + 函数嵌套 + 闭包
装饰器原则:不改变源代码和函数的调用方式
高阶函数:函数参数是一个函数名或者返回的是函数名
函数嵌套:在一个函数中又定义了一个新的函数
闭包:在函数嵌套中,内部函数对外部函数作用域变量的引用(除去全局变量),则称内部函数为闭包。
举个例子,假如你有一个已经上线的网站,之前是免费开放的,有一天你不希望免费开放了,你想让大家登陆后才能访问,你又不能修改源码,此时就要用到装饰器了。