Python 容器 | 可迭代对象 | 迭代器 | 生成器 | range

容器

常见的容器

  1. 列表 (List)
  2. 元组 (Tuple)
  3. 字典 (Dict)
  4. 字符串 (Str)
  5. 集合 (Set)

特点

容器是一个可以用来承装各种对象的Python对象, 最直观的体现是我们可以使用关键字 innot 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

注:

  1. __contains__ 方法必须含有两个参数,否则在使用该方法时将会报错。

TypeError: contains() takes 1 positional argument but 2 were given

  1. 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

总结
  1. 使用 Object.__contains__()
    该种使用方法可以控制返回的值的具体内容。
  2. 使用 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')

注:

  1. __iter__ 方法需要返回一个对象,一个迭代器对象。若 __iter__ 方法为返回一个对象解释器将抛出异常。
  2. 如何检测我们创建的对象是否为可迭代对象呢?可以通过 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

  1. 让我们检测可迭代对象是否可以被 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

【生成器函数内部代码已执行完毕】

注:

  1. 生成器对象仅能够将其中所有元素遍历一遍,因此上述代码的执行结果中,【生成器函数内部代码已执行完毕】 仅被打印了一次。
  2. 方括号的出现可以很好的帮助理解生成器对象的执行过程。

检测一个对象是否为生成器对象

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

注:

  1. 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’>

注:

  1. 将列表表达式中的中括号换成小括号并不会成为 ”元组表达式“ 而是创建了一个生成器对象。
  2. 无论是迭代器还是生成器都可以使用 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 的出现打破了我的认知,至今依旧不知道原理,若有读者对此十分了解,还望不吝赐教。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BinaryMoon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值