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__
则表示这个对象或者这个类是可迭代对象。
方法二:还可以通过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))
掌握了这个方法,也可以用来判断一下文件对象是否是可迭代对象。
with open('./doc/passwd.txt', 'r') as f:
print(isinstance(f, Iterable))
执行结果:
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变成了迭代器。
s = 'hello'
s_iter = s.__iter__()
print(isinstance(s, Iterator))
print(isinstance(s_iter, Iterator))
此时,想要获取这个迭代器中的值时,需要调用next()函数
loop0 = str_iter.__next__()
print(loop0)
loop1 = str_iter.__next__()
print(loop1)
这样每调用一次next()方法,才能得到一个值,如何实现对这个字符串的for循环?
使用while循环+迭代器实现for循环
代码如下:
str = 'hello'
str_iter = iter(str)
while True:
try:
loop = str_iter.__next__()
print(loop)
except:
print("字符串遍历结束")
break
这里需要注意一个问题,如果在遍历结束之后,再执行一次对字符串的遍历,会出现如下情况:
这是因为: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
执行效果:
1.3 迭代器和生成器的区别
迭代是访问容器元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
可迭代对象可以直接作用于for循环的对象(如何判断是否可以迭代?)
- 一类是集合数据类型,如list, tuple,dict, set,str等;
- 一类是generator,包括生成器和带yield的generator function。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数
2. 闭包
2.1 闭包概念
闭包就是指有权访问另一个函数作用域中的变量的函数。
创建闭包最常见方式,就是在一个函数内部创建另一个函数。
常见形式: 内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用。
闭包的一个常用场景就是装饰器。
总结一下闭包需要满足的三个条件:
- 函数内定义一个函数
- 内部函数使用了外部函数的临时变量
- 外部函数的返回值是内部函数的引用(指的就是内部的函数名)
在代码中,如何检测函数是否是闭包?可以使用__closure__函数,返回cell就是闭包,返回None就不是闭包。
def func1():
a = 2
def func2():
print(a)
print(func2.__closure__)
return func2
func1()
执行结果:
说明func2是一个闭包。因此内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
2.2 nonlocal关键字
如果对上面的例子稍加修改,如下:
def func1():
a = 2
def func2():
a += 5
print(a)
return func2
tmp = func1()
tmp()
会出现如下报错:
这是因为:在 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()
执行效果:
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中,有一个帮我们绘制图形的模块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文件,选择用任意一个浏览器显示这个文件内容
内容显示就是我们需要的图形
也可以用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规定,如果在内部函数中访问了外层函数的变量,那么这个变量将不会消亡,将会常驻内存。也就是说,使用闭包,可以保证外层函数中的变量在内存中常驻。
再看一个我们之前做过微博的爬虫代码
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( )就可以了。
综上, 闭包的作用就是让一个变量能够常驻内存. 供后面的程序使用。