1.迭代器与可迭代对象

1.1 可迭代对象Iterable

可以直接作用于for循环的对象称为可迭代对象Iterable
一类是集合数据类型,如list, tuple,dict, set,str等;
一类是generator,包括生成器和带yield的generator function。

在代码中,如何判断是否是可迭代对象?
方法一:我们可以通过dir函数来查看类中定义好的所有方法

str = 'hello'
print(dir(str))    #打印对象中的方法和函数
print(dir(list))   #打印类中的方法和函数

如果打印的结果中含有__iter__

python 两个可迭代对象 同时遍历_迭代器


则表示这个对象或者这个类是可迭代对象。

方法二:还可以通过isinstence( )函数来查看一个对象是什么类型的

【注】

py2: from collections import Iterable

py3: from collections.abc import Iterable

我的实验环境是python3.7,所以这里使用collections.abc

from collections.abc import Iterable
from collections.abc import Iterator

print(isinstance(123, Iterable))
print(isinstance('hello', Iterable))
print(isinstance('hello', Iterator))

python 两个可迭代对象 同时遍历_迭代_02


掌握了这个方法,也可以用来判断一下文件对象是否是可迭代对象。

with open('./doc/passwd.txt', 'r') as f:
    print(isinstance(f, Iterable))

执行结果:

python 两个可迭代对象 同时遍历_内部函数_03

1.2 迭代器Iterator

可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator。
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数

如:

str = 'hello'
str_iter = iter(str)
print(str_iter)
print(type(str_iter))

使用iter()函数后,str变成了迭代器。

python 两个可迭代对象 同时遍历_内部函数_04

s = 'hello'
s_iter = s.__iter__()
print(isinstance(s, Iterator))
print(isinstance(s_iter, Iterator))

python 两个可迭代对象 同时遍历_python 两个可迭代对象 同时遍历_05

此时,想要获取这个迭代器中的值时,需要调用next()函数

loop0 = str_iter.__next__()
print(loop0)
loop1 = str_iter.__next__()
print(loop1)

python 两个可迭代对象 同时遍历_迭代器_06


这样每调用一次next()方法,才能得到一个值,如何实现对这个字符串的for循环?

使用while循环+迭代器实现for循环

代码如下:

str = 'hello'
str_iter = iter(str)

while True:
    try:
        loop = str_iter.__next__()
        print(loop)
    except:
        print("字符串遍历结束")
        break

python 两个可迭代对象 同时遍历_迭代器_07


这里需要注意一个问题,如果在遍历结束之后,再执行一次对字符串的遍历,会出现如下情况:

python 两个可迭代对象 同时遍历_python 两个可迭代对象 同时遍历_08


这是因为:next方法从上一次停止的地方继续执行,只能向后算

再看一个例子:

for i in [1,2,3]:
	print(i)

等同于:

lst = [1, 2, 3]
lst_iter = lst.__iter__()
while True:
    try:
        i = lst_iter.__next__()
        print(i)
    except StopIteration:
        break

执行效果:

python 两个可迭代对象 同时遍历_迭代_09

1.3 迭代器和生成器的区别

迭代是访问容器元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
可迭代对象可以直接作用于for循环的对象(如何判断是否可以迭代?)

  • 一类是集合数据类型,如list, tuple,dict, set,str等;
  • 一类是generator,包括生成器和带yield的generator function。

python 两个可迭代对象 同时遍历_内部函数_10


可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数

2. 闭包

2.1 闭包概念

闭包就是指有权访问另一个函数作用域中的变量的函数。
创建闭包最常见方式,就是在一个函数内部创建另一个函数。
常见形式: 内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用。
闭包的一个常用场景就是装饰器。

总结一下闭包需要满足的三个条件

  1. 函数内定义一个函数
  2. 内部函数使用了外部函数的临时变量
  3. 外部函数的返回值是内部函数的引用(指的就是内部的函数名)

在代码中,如何检测函数是否是闭包?可以使用__closure__函数,返回cell就是闭包,返回None就不是闭包。

def func1():
    a = 2
    def func2():
        print(a)
    print(func2.__closure__)
    return func2

func1()

执行结果:

python 两个可迭代对象 同时遍历_python 两个可迭代对象 同时遍历_11


说明func2是一个闭包。因此内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

2.2 nonlocal关键字

如果对上面的例子稍加修改,如下:

def func1():
    a = 2
    def func2():
        a += 5
        print(a)
    return func2

tmp = func1()
tmp()

会出现如下报错:

python 两个可迭代对象 同时遍历_迭代器_12


这是因为:在 Python 中,内层函数对外层作用域中的变量仅有只读访问权限

对于func1函数,x是局部变量,对于func2函数,x是非全局的外部变量。当在func2中对x进行修改时,会将x视为func2的局部变量,屏蔽掉func1中对x的定义;如果仅仅在func2中对x进行读取,则不会出现这个错误。

nonlocal关键字可以使我们自由地操作外层作用域中的变量,其作用是:显式的指定变量不是闭包的局部变量

def func1():
    a = 2
    def func2():
        nonlocal a
        a += 5
        print(a)
    return func2

tmp = func1()
tmp()

执行效果:

python 两个可迭代对象 同时遍历_迭代器_13

2.3 闭包案例之一元二次方程

def line_conf(a, b):
    def line(x):
        return a * x + b
    return line

函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值。这样,我们就确定了函数的最终形式(例如:y=x+1和y=4x+5)。

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
#指定x值
print(line1(5))
#打印1*5+1
print(line2(5))
#打印4*5+5

执行结果:

python 两个可迭代对象 同时遍历_迭代器_14


在python中,有一个帮我们绘制图形的模块pyecharts。下面我们就用这个模块,绘制出y=x+1和y=4x+5这两条一元二次方程的在坐标轴上的图。

需要安装两个模块—>1).pyecharts (0.5.11版本) 2).pyecharts-snapshot

代码实现如下:

def line_conf(a, b):
    def line(x):
        return a * x + b
    return line


# 一元线性方程x+1
line1 = line_conf(1, 1)
# 一元线性方程4x+5
line2 = line_conf(4, 5)

#计算y轴
loopCount = 100
y1 = [line1(item) for item in range(loopCount)]
y2 = [line2(item) for item in range(loopCount)]

# 图形绘制(pyecharts==0.5.11)===导入绘制折线图的类
from pyecharts import Line

# 创建绘制折线图的对象lineObj
x = list(range(loopCount))  # x轴坐标必须是一个列表;
lineObj = Line(title="一元线性方程图形展示")
lineObj.add(name='y=x+1', x_axis=x, y_axis=y1)
lineObj.add(name='y=4x+5', x_axis=x, y_axis=y2)
# 将绘制的图形显示为html文件
lineObj.render('./doc/line.html')

执行成功后,打开这个html文件,选择用任意一个浏览器显示这个文件内容

python 两个可迭代对象 同时遍历_迭代_15


内容显示就是我们需要的图形

python 两个可迭代对象 同时遍历_迭代_16


也可以用matplotlib模块绘图。

import matplotlib.pyplot as plt

def line_conf(a:int,b:int):
    def line(x):
        return a*x+b
    return line

print(line_conf(1,1))  #返回的是一个函数名地址 <function line_conf.<locals>.line at 0x00000249361B2828>
line1 = line_conf(2,1)
line2 = line_conf(3,4)
line3 = line_conf(4,5)

x = list(range(100))
y1 = [line1(item) for item in range(100)]
y2 = [line2(item) for item in range(100)]
y3 = [line3(item) for item in range(100)]

plt.plot(x,y1,label = 'y1=2x+1')
plt.plot(x,y2,label = 'y2=3x+4')
plt.plot(x,y3,label = 'y3=4x+5')

#绘制折线图
plt.title('line display')   #添加标题
plt.legend()                #添加图例
plt.grid(alpha=0.3)         #添加网格,alpha是透明度
plt.show()

效果如下:

python 两个可迭代对象 同时遍历_迭代_17

那么,什么时候使用闭包呢?使用闭包又有什么好处?
之前学习过,如果一个函数执行完毕,则这个函数中的变量会被销毁,而在闭包中,如果变量被销毁了,那么内部函数将不能正常执行。所以python规定,如果在内部函数中访问了外层函数的变量,那么这个变量将不会消亡,将会常驻内存。也就是说,使用闭包,可以保证外层函数中的变量在内存中常驻。
再看一个我们之前做过微博的爬虫代码

def but():
    url = 'https://m.weibo.cn/api/container/getIndex?type=uid&value=1913330992&containerid=1005051913330992'
    content = requests.get(url).text
    def get_content():
        if content:
            return content
    print(get_content.__closure__)
    return get_content
    
result = but()
content = result()
print(content)

在外部函数but()中,我们获取到要爬取的地址,并加载内容。后面如果再需要这里面的内容就不需要再执行非常耗时的网络连接操作了。想获取内容,直接执行content = result( )就可以了。
综上, 闭包的作用就是让一个变量能够常驻内存. 供后面的程序使用。