Python 容器 | 可迭代对象 | 迭代器 | 生成器 | range
容器
常见的容器
- 列表 (List)
- 元组 (Tuple)
- 字典 (Dict)
- 字符串 (Str)
- 集合 (Set)
特点
容器是一个可以用来承装各种对象的Python对象, 最直观的体现是我们可以使用关键字 in 及 not in 来判断一个元素是否存在于容器中。
例如:
LIST = list('RedHeart')
TUPLE = tuple(LIST)
STR = 'RedHeart'
SET = set('RedHeart')
print('R' in LIST) # True
print('H' not in TUPLE) # False
print('a' in STR) # True
print('x' in SET) # False
注:
这里我们没有演示字典的 in 操作,是因为它比较特殊,需要挑出来重点讲一下。
DICT = dict(MSF = 'Metasploit-Framework', BP = 'BurpSuite')
print(BP in DICT) # True
print('BurpSuite' in DICT) # False
print(DICT['BP'] in DICT) # False
我们可以看到使用 print(DICT[‘BP’] in DICT) 返回的却是False。这是因为字典的 in 操作,默认针对的是字典的键。也就是说,print(DICT[‘BP’] in DICT) 等价于 print(DICT[‘BP] in DICT.keys())。如果想使用针对字典的值的 in 操作,可以使用 print(DICT[‘BP’] in DICT.values()) 。
构造容器对象
我们可以通过为自己创建的容器对象添加 __contains__ 方法来为对象实现 in 操作。只有添加了 contains 方法的对象才可以使用 Python 内置的运算符 in。
注:
- __contains__ 方法必须含有两个参数,否则在使用该方法时将会报错。
TypeError: contains() takes 1 positional argument but 2 were given
- in 操作符前的内容将作为实参传递给 __contains__ 中的形参,该过程由 Python 解释器执行。
深入原理
class People:
def __init__(self, content):
self.content = content
def __contains__(self, arg):
return '【返回值】'
man = People('RedHeart')
print(man.__contains__(''))
打印结果:
【返回值】
我们再来看一下另一段代码:
class People:
def __init__(self, content):
self.content = content
def __contains__(self, arg):
return '【返回值】'
man = People('RedHeart')
print('' in man)
打印结果:
True
总结
- 使用 Object.__contains__()
该种使用方法可以控制返回的值的具体内容。 - 使用 in
该种使用方法的返回值为返回内容的布尔值化后的结果。
该段代码可以与上一段代码进行对比,加深理解。
class People:
def __init__(self, content):
self.content = content
def __contains__(self, arg):
return ''
man = People('RedHeart')
print('' in man)
打印结果:
False
将返回值由 【返回值】 替换为 ‘’,其他内容并未修改,但返回值却发生了变化。
这是因为:
在 Python 中,非空字符串的布尔值化结果为 True, 而空字符串的布尔值化结果为 False。
使自建函数成为“容器”
class People:
def __init__(self, content):
self.content = content
def __contains__(self, target):
return target in self.content
man = People('RedHeart')
print('R' in man)
print('X' in man)
print('' in man)
print('' in 'string')
打印结果:
Ture
False
True
True
注:
空字符串 ‘’ 存在于任何字符串容器中。
可迭代对象
要理解什么是可迭代对象可以先从理解什么是迭代入手。
迭: 轮流。
代: 代替。
故迭代的意思可以理解为 轮流代替 。因此,可迭代对象可以这样理解:
可迭代对象的每一个元素都将被轮到以代替上一个元素(第一个元素除外)。这意味这可迭代对象中的每一个元素都可以被单独的拿出来以执行某种操作。
常见的可迭代对象
本文上方讲到的五个容器均为可迭代对象,除此之外,可迭代对象还包括生成器。生成器将在本文后续内容中讲述。
特点
可迭代对象的特点是其可以使用 for Element in Object: 语句遍历可迭代对象中的每一个元素。例如:
STR = 'RedHeart'
for char in STR:
print(char)
打印结果:
R
e
d
H
e
a
r
t
构造可迭代对象
让自己构造的对象成为为可迭代对象,只需为其编辑 iter 方法即可。
class People():
def __init__(self, name):
self.name = name
def __iter__(self):
return self
redHeart = People('RedHeart')
注:
- __iter__ 方法需要返回一个对象,一个迭代器对象。若 __iter__ 方法为返回一个对象解释器将抛出异常。
- 如何检测我们创建的对象是否为可迭代对象呢?可以通过 Python 标准库 collections 中的对象 Iterable 与 Python 内置函数 isinstance 来检测一个对象是否为可迭代对象。
from collections import Iterable
class People():
def __init__(self, name):
self.name = name
def __iter__(self):
return self
redHeart = People('RedHeart')
print(isinstance(redHeart, Iterable))
print(isinstance(list('RedHeart'), Iterable))
print(isinstance(36, Iterable))
打印结果:
True
True
False
- 让我们检测可迭代对象是否可以被 for循环 遍历。
for i in redHeart:
print(i)
抛出错误:
TypeError: iter() returned non-iterator of type ‘People’
之所以会出现这个错误,是因为仅仅只是可迭代对象(不是生成器或迭代器)并不能够正常使用 for循环 (可迭代对象 range 对象除外),在使用 for Element in Object: 语句时,Python 会调用 Object 的 ** iter_** 方法以返回一个迭代器对象,但可迭代对象还需要一个 **__next_**
方法才能成为迭代器对象。
迭代器
迭代器一定是可迭代对象,但可迭代对象不一定是迭代器对象。若想了解迭代器对象,应先对可迭代器对象能够有一个初步认识。
特点
迭代器对象可以通过使用 Python 内建函数 next() 来遍历每一个元素。可以通过使用 Python 内建函数 iter() 来将可迭代对象转换为迭代器对象,前提是该迭代器对象定义了 __next__ 方法。
迭代器只能往前不会退后,因此同一迭代器中的同一元素仅能被遍历一次。
检测一个对象是否为迭代器对象
from collections import Iterator
# 请不要担心,这段代码在后续将会讲解
class People():
def __init__(self, name):
self.name = name
def __iter__(self):
return self
def __next__(self):
print(self.name)
raise StopItoration()
redHeart = People('RedHeart')
print(isinstance(redHeart, Iterator))
True
使用 next() 函数遍历每一个元素 | 使用 iter() 函数将定义了 _next_ 的可迭代对象转换为迭代器对象
name = list('RedHeart')
print(type(name))
name_itr = iter(name)
print(type(name_itr))
for char in range(len(name)):
print(next(name_itr))
打印结果:
<class ‘list’>
<class ‘list_iterator’>
R
e
d
H
e
a
r
t
未定义 _next_ 方法
class People():
def __init__(self):
pass
def __iter__(self):
return self
redHeart = People()
print(type(iter(redHeart)))
抛出错误:
TypeError: iter() returned non-iterator of type ‘People’
刨析 for 循环原理
在对可迭代对象进行 for 循环操作时,Python 解析器将使用内建函数 iter() 将可迭代对象转换为迭代器对象,在每一次循环过程中都将调用可迭代对象的 __next__ 方法来遍历对象中的每一个元素。
若想使自建迭代器对象能正常工作,需要在合适的时间抛出 StopIteration 错误来终止循环,否则将无限循环下去(即死循环)。
class People():
def __init__(self, name):
self.name = name
self.length = len(name)
self.counter = 0
def __iter__(self):
return self
def __next__(self):
if self.counter < self.length:
print(self.name[self.counter])
self.counter += 1
else:
raise StopIteration()
redHeart = People('RedHeart')
for char in iter(redHeart):
next(redHeart)
R
e
d
H
e
a
r
t
仿 Python 内建函数 range
class RangeLike():
def __init__(self, start, stop, step):
self.start = start
self.stop = stop
self.step = step
self.counter = start
def __iter__(self):
if self.step > 0:
return self
else:
# 抛出语法错误信息
raise SyntaxError()
def __next__(self):
tmp = self.counter + self.step
if tmp < self.stop:
self.counter = tmp
return tmp
else:
raise StopIteration()
if __name__ == '__main__':
for i in RangeLike(0, 36, 3):
print(i)
打印结果:
3
6
9
12
15
18
21
24
27
30
33
注:
range() 函数返回的是可迭代对象而不是迭代器对象。
使用迭代器常犯的错误
abbr = ['BP', 'MSF', 'NMap', 'WS']
tool = ['BurpSuite', 'Metasploit-Framework', 'NetworkMapper', 'WireShark']
package = zip(abbr, tool)
print(package)
# 见证奇迹的时刻
print(list(package))
print(list(package))
打印结果:
<zip object at 0x0000025B45D20700>
[(‘BP’, ‘BurpSuite’), (‘MSF’, ‘Metasploit-Framework’), (‘NMap’, ‘NetworkMapper’), (‘WS’, ‘WireShark’)]
[]
为什么两次打印的结果会不一样?
由于 Python 内建函数 zip() 返回的是一个迭代器,在将 zip 对象转换为 list 对象的过程中需要调用该对象的 __next__ 来遍历每一个元素。当 zip 对象转换为 list 后,__next__ 已经指向了最后一个索引之后的位置,再使用 list 函数将 zip 对象转换为 list 对象时,抛出的 StopIteration 错误将被捕获,而迭代器也将停止运作,因此返回了空列表。
生成器
在 Python 中,有一类特殊的函数,这类函数都使用了 yield 关键字。使用这类函数将返回一个生成器对象。
生成器对象是一个特殊的可迭代对象,使用 Python 内置函数 next() 将会执行生成器函数内部的代码,当遇到关键字 yield 时将返回一个值并退出生成器函数,相当于 return。但与 return 不同的是,在下一次调用 next() 函数时,将执行上一个 yield 到下一个 yield 之间(包括第二个 yield 语句但不包括第一个 yield 语句)的代码并退出生成器函数。直到将生成器函数内部的代码全部执行完毕。
def RangeLike(start, end, step):
while True:
if start < end:
print('【')
yield start
start += step
print('】')
else:
print('【生成器函数内部代码已执行完毕】')
break
generator_function = RangeLike(0, 15, 3)
for i in generator_function:
print(i)
for j in generator_function:
print(j)
打印结果:
【
0
】
【
3
】
【
6
】
【
9
】
【
12
】
【生成器函数内部代码已执行完毕】
注:
- 生成器对象仅能够将其中所有元素遍历一遍,因此上述代码的执行结果中,【生成器函数内部代码已执行完毕】 仅被打印了一次。
- 方括号的出现可以很好的帮助理解生成器对象的执行过程。
检测一个对象是否为生成器对象
from inspect import isgenerator
def creater(start, stop, step):
while True:
tmp = start + step
if tmp < stop:
yield tmp
start += step
else:
break
print(isgenerator(creater(0, 11, 1)))
打印结果:
True
创建生成器的两种方式
使用生成器函数创建
def creater(start, stop, step):
while True:
tmp = start + step
if tmp < stop:
yield tmp
start += step
else:
break
判断一个函数是否为生成器函数
from inspect import isgeneratorfunction
def creater(start, stop, step):
while True:
tmp = start + step
if tmp < stop:
yield tmp
start += step
else:
break
print(type(creater))
print(isgeneratorfunction(creater))
print(type(creater(0, 11, 1)))
print(isgeneratorfunction(creater(0, 11, 1)))
打印结果:
<class ‘function’>
True
<class ‘generator’>
False
注:
- isgeneratorfunction 只能检测目标是否为生成器函数,而无法检测其是否为生成器对象。
使用类似列表表达式的方式进行创建
arr = [i for i in range(11)]
creater = (i for i in range(11))
print(type(arr))
print(type(creater))
打印结果:
<class ‘list’>
<class ‘generator’>
注:
- 将列表表达式中的中括号换成小括号并不会成为 ”元组表达式“ 而是创建了一个生成器对象。
- 无论是迭代器还是生成器都可以使用 Python 的内置函数next() 或使用方法 __next__() 来遍历其中的每一个元素,而这也是 for 原理的一部分。
Python 基本序列 — range
Python 中有三个基本的序列类型,分别是 列表(list),元组(tuple)以及range。range序列相比于其他两种基本序列类型更加特别。
惰性求值与严格求值
惰性求值也称为延迟求值,特点是创建了惰性求值的数据结构后并不会立即将全部数据计算出来,而是在需要的时候才会去计算。迭代器与生成器就是 Python 中惰性求值的数据结构,仅在调用该类对象的 __next__() 方法或将对象作为参数调用 Python 内置函数 next() 后才会进行下一步计算。
range 既不是生成器也不是迭代器,是一个可迭代对象。关于这句话的正确性可以采用前面讲到的验证方法进行验证。
容器 range
range 并不只是一个可迭代对象,还是一个容器,可以通过 in 操作来判断一个元素是否存在于 range 中。
print(36 in range(37))
打印结果:
True
索引 range
range 对象中的元素除了可以通过遍历进行访问,还可以通过索引来进行访问。
print(range(37)[36])
打印结果:
36
range 的 for 循环操作
creater = range(0, 7, 2)
for i in creater:
print(i)
for j in creater:
print(j)
打印结果:
0
2
4
6
0
2
4
6
注:
range 对象并不像迭代器对象与生成器对象那般,同一个对象只能遍历一遍,同一个 range 对象能够被遍历无数遍。
本以为不是迭代器和生成器的可迭代对象是不可以进行 for 循环操作的,可 range 的出现打破了我的认知,至今依旧不知道原理,若有读者对此十分了解,还望不吝赐教。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84057.html