可迭代对象

4.9 可迭代对象

Python中有6种基本的数据类型,除了数字类型,其它5种类型都是可迭代对象。掌握可迭代对象的特性和方法是非常有必要的。

4.9.1 可迭代(iterable)、可迭代对象

4.9.1.1 相关概念

迭代(遍历)就是按照某种顺序逐个访问对象中的每一项。
Python中有很多对象都是可以通过for语句来直接遍历的,例如list、string、dict等等,这些对象都是可迭代的,被称为可迭代对象。
可以将可迭代对象想象成一个容器,里面存放了有限个元素,并且每个元素都可以从中获取出来。那么这个容器就是可迭代的,这个容器就是可迭代对象。
所有的可迭代对象都需要实现__iter__方法,该方法就是用于当我们在循环时将可迭代对象转换成迭代器的。

4.9.1.2 可迭代对象的判断

  1. 可以通过hasattr(obj, 'iter')来判断obj是否为可迭代对象。
  2. 可以通过'iter' in dir(obj)来判断obj是否为可迭代对象。
  3. 也可以通过isinstance来进行判断:
from typing import *
dct = {'one': 1, 'two': 2, 'three': 3, 'zero': 0}
print(isinstance(dct, Iterable))

True

4.9.1.3 可迭代对象的特点

  1. 私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)
  2. 直观,可以直接看到里面的数据
  3. 占用内存
  4. 不能直接通过循环迭代取值(需要先通过__iter__方法将其转换成迭代器)
    当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。

4.9.2 迭代器(iterator)

4.9.2.1 相关概念

迭代器可迭代对象的一个子集。是一个可以记住遍历的位置的对象,它与列表、元组、集合、字符串这些可迭代对象的区别就在于__next__()方法的实现。也就是通过该方法可以一个个的将元素取出来。
迭代器支持__iter__()__next__()方法。其中:
__iter__()方法返回迭代器对象本身,而可迭代对象的该方法则返回其迭代器。
__next__()方法返回容器的下一个元素,在结尾时引发StopIteration异常。

4.9.2.1.1 迭代的本质

遍历的本质其实就是在我们使用for或者while循环可迭代对象时会自动调用该可迭代对象的__iter__()方法得到其迭代器,然后通过这个迭代器的__next__()方法一步步获取下一个元素。直到最后一个元素引发StopIteration异常。
对于迭代器,其__next__()方法和next(迭代器)等效,都是用来获取下一个元素。

4.9.2.2 迭代器的创建

4.9.2.2.1 通过iter()函数创建迭代器

通过iter函数可以很简单的将可迭代对象转换成迭代器。
示例1:
>>> from typing import *
>>> iterable = [1, 2, 4]
>>> it = iter(iterable)
>>> print(isinstance(it, Iterable)) # 是否可迭代对象
>>> print(isinstance(it, Iterator)) # 是否迭代器

True
True

示例2:

s = 'ABC'  # s此时是一个可迭代对象
it = iter(s)  # 转换成迭代器,如果此时报错,则表示s不是一个可迭代对象。
for i in range(len(s)):
    print(s[i])  # 遍历s中的所有元素
print(it.__next__)
print(it.__next__())  # 迭代获取元素
print(next(it))  # 继续迭代获取元素

A
B
C
<method-wrapper 'next' of str_iterator object at 0x0000013D53672D40>
A
B

4.9.2.2.2 通过filter函数创建

参见可迭代对象相关函数的filter函数

4.9.2.2.3 通过map函数创建

参见可迭代对象相关函数的map函数

4.9.2.2.4 通过itertools创建

除了Python内置的iter之外,还可以通过Python内置的工具包itertools创建迭代器,其中函数包括:

count
cycle
repeat
accumulate
chain
compress
dropwhile
islice
product
permutations
combinations
......

itertools中包含很多用于创建迭代器的实用方法,如果感兴趣可以访问官方文档进行详细了解。

4.9.2.2.5 将可迭代对象改造成迭代器对象
class iterator_list(object):
    def __init__(self,a):
        self.a=a
        self.len=len(self.a)
        self.cur_pos=-1
    def __iter__(self):
        return self
    def __next__(self):
        self.cur_pos +=1
        if self.cur_pos<self.len:
            return self.a[self.cur_pos]
        else:
            raise StopIteration() # 表示至此停止迭代

4.9.2.3 迭代器的判断

  1. 可以通过hasattr(obj, 'next')来判断obj是否为迭代器。
  2. 可以通过'next' in dir(obj)来判断obj是否为迭代器。
  3. 可以通过isinstance(obj, Iterator)来进行判断是否为迭代器(需要导入typing模块)。

4.9.2.4 迭代器的特点

  1. 可以直接通过循环迭代取值
  2. 数据不直观,操作方法单一
  3. 迭代器是一种支持next()操作的对象。它包含了一组元素,当执行next()操作时,返回其中一个元素。当所有元素都被返回后,再执行next()报异常:StopIteration
  4. 迭代器是访问集合元素的一种方式
  5. 迭代器对象表示的是一个数据流,可以把它看作一个有序序列,但我们不能提前知道序列的长度,只有通过nex()函数实现需要计算的下一个数据。
  6. 成员资格检查时,会从迭代器中按顺序获取元素并判断,当能有判断结果时,会记录当前的位置。
r = iter(range(10))
print(5 in r)
for i in r:
    print(i)

True
6
7
8
9

r = iter(range(10))
print(5 in r)
print(5 in r)

True
False

r = iter(range(10))
print(11 in r)
for i in r:
    print(i)

False

你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择,节省内存,按需取值。

4.9.3 生成器(generator)

4.9.3.1 相关概念

生成器迭代器的子集,生成器一定是迭代器,但是迭代器不全是生成器。在Python中一个函数可以用yiled或yiled from替代return返回值,这样的话这个函数就变成了一个生成器对象。

4.9.3.2 生成器的判断

可以通过isinstance(obj, Generator)来进行判断是否为迭代器(需要导入typing模块)。

4.9.3.3 生成器的创建

4.9.3.3.1 通过函数创建(生成器函数)

通过yield

def fib(number):
    n, a, b = 0, 0, 1
    while n < number:
        yield b
        a, b = b, a + b
        n += 1

gen = fib(5)
print(gen)
print(type(gen))

<generator object generator at 0x0000025EAEEF1EE0>
<class 'generator'>

def func():
    print('111')
    yield 222
    print('123')
    yield 1211


gener = func()
print(gener.__next__())
print(gener.__next__())
print(gener.__next__())  # 会报错

111
222
123
1211
Traceback (most recent call last):
File "C:\Users\furongbing\PycharmProjects\pythonProject\t2.py", line 11, in
print(gener.next()) # 会报错
StopIteration

通过yield from

def generator(array):  # 其中:array为可迭代对象
    for sub_array in array:
        yield from sub_array


gen = generator([1, 2, 3])
print(gen)
print(type(gen))

<generator object generator at 0x00000251434483C0>
<class 'generator'>

yield from相对于yield的有几个主要优点:

  • 代码更加简洁
  • 可以用于生成器嵌套(yield直接返回后面的内容,yield from可以将后面跟着的迭代对象当成生成器返回)
  • 易于异常处理
    yield 与 return 的区别:
  • return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
  • yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。
4.9.3.3.2 通过推导式创建(生成器表达式)

>>> gen = (x*2 for x in range(10))
>>> gen
>>> type(gen)

<generator object generator at 0x00000251434483C0>
<class 'generator'>

列表推导式和生成器推导式的区别:

  • 列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素。
  • 列表推导式一目了然,生成器表达式只是一个内存地址。
    生成器的优点:
  • 代码简洁
  • 运行速度快
  • 节省内存

4.9.3.4 生成器的特点

  1. 生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。目的就是用来生成元素的。
  2. 对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。而且记录了程序执行的上下文。
  3. 生成器不仅记住了它的数据状态,生成器还记住了程序执行的位置。
  4. 生成器一定是可迭代的,也一定是迭代器对象。

4.9.4 可迭代对象的通用操作

4.9.4.1 可迭代对象的遍历

可迭代对象都可以进行遍历的操作。就是将其中的元素一个个取出来进行操作。

Iter = 'Python'
for i in Iter:
    print(i)

P
y
t
h
o
n

对于序列,不但能遍历里面所有的元素,还能遍历元素的索引。关于序列,将在序列中详细介绍。
用range()和len()函数遍历得到索引和值

seq = 'Python'
for i in range(len(seq)):
    print(f'索引:{i}, 元素:{seq[i]}')

索引:0, 元素:P
索引:1, 元素:y
索引:2, 元素:t
索引:3, 元素:h
索引:4, 元素:o
索引:5, 元素:n

通过enumerate函数遍历得到索引和值

seq = 'Python'
for i, v in enumerate(seq):
    print(f'索引:{i}, 元素:{v}')

索引:0, 元素:P
索引:1, 元素:y
索引:2, 元素:t
索引:3, 元素:h
索引:4, 元素:o
索引:5, 元素:n

使用zip函数同时遍历多个序列

seq1 = 'Python'
seq2 = 'Java'
for q, a in zip(seq1, seq2):
    print(q, a)

P J
y a
t v
h a

注意:上述用字符串做演示,实际上其它序列对象也是一样的

4.9.4.2 成员资格检查

可以用innot in检查某个元素是否在目标可迭代对象中。可迭代对象都支持成员资格检查操作。
>>> Iter = 'Python'
>>> 'P' in Iter
>>> 'a' not in Iter

True
True

注意:上述用字符串做演示,实际上其它可迭代对象也是一样的

4.9.4.3 解包(拆包)、装包

4.9.4.3.1 概念

解包的英文是unpacking,就是将容器里的元素全部取出来。在Python中,所有的可迭代对象都支持解包。有些情况中,解包会自动完成,在另外一些情况中,可以手动进行解包。
装包和解包相反,就是将一些元素打包放到一起。

4.9.4.3.2 自动解包

在进行变量赋值的时候,如果变量的数量和右边可迭代对象中元素的数量相同时,解包会自动完成。

a, b = [1, 2]
print(a, b)
c, d = {'one': 1, 'two': 2}
print(c, d)

1 2
one two

我们在进行变量赋值的时候,经常会用到下面这种方式,同时为不同的变量赋值不同的数据:
>>> e, f = 5, 6
实际上这就是利用到了解包。后面的5, 6其实就是可迭代对象(元组),然后进行自动解包后赋值给了变量e和f。
这里值得注意的是,字典在进行自动解包时获得的是字典的key。

4.9.4.3.3 手动解包

可以通过在可迭代对象前面添加*符号进行手动解包,解包后的结果是可迭代对象中所包含的所有的元素。
因为是多个元素,所以不能对解包后的结果使用len等只支持单个参数的函数。否则会报错。但是可以使用max等可以接收多个参数的函数。
手动解包一般是用在函数接收参数中。

lst = [1, 2, 4]
print(*lst)
print(max(*lst))
print(len(*lst))

1 2 4
4
Traceback (most recent call last):
File "E:\studypy\tmp5.py", line 4, in
print(len(*lst))
TypeError: len() takes exactly one argument (3 given)

4.9.4.3.4 装包

在赋值或者函数传参时,如果在等式左边变量名添加*则可以进行装包。

lst = [1, 2, 4, 8, 16]
dct = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
a, b, *c = lst
print(a, b, c)
d, *e, f = dct
print(d, e, f)

1 2 [4, 8, 16]
one ['two', 'three'] four

dct = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
a, *b, c = dct.items()
print(a, b, c)

('one', 1) [('two', 2), ('three', 3)] ('four', 4)

*进行装包后的变量类型其实就是列表,在函数中则是元组。函数的解包与装包将会在函数章节中详细介绍。

4.9.5 可迭代对象相关函数

4.9.5.1 next(Iterator[,default])

描述
通过调用迭代器的__next__()方法从迭代器中检索下一项。如果给出了默认值,则在迭代器耗尽时返回默认值,否则将引发 StopIteration。
示例

a = iter([1, 2, 3])
print(next(a))
print(next(a))
print(next(a))
print(next(a, '无数据了'))

1
2
3
无数据了

4.9.5.2 all(iterable)、any(iterable)

描述
对于all函数,如果 iterable 的所有元素均为真值或可迭代对象为空则返回 True。否则返回False。any函数则是只要有一个元素为真值,则返回True,否则返回False。若iterable为空,也返回False。
示例

a = []
b = [1, 2]
c = [1, 0]
d = [0, 0]
print(all(a))
print(all(b))
print(all(c))
print(all(d))
print(any(a))
print(any(b))
print(any(c))
print(any(d))

True
True
False
False
False
True
True
False

4.9.5.3 enumerate(iterable, start=0)

描述
返回一个枚举对象。iterable 必须是一个序列,或 iterator,或其他支持迭代的对象。 enumerate() 返回的迭代器的 next() 方法返回一个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。
示例

dct = {'one': 1, 'two': 2}
for i, j in enumerate(dct):
    print(i, j)

0 one
1 two

4.9.5.4 filter(function, iterable)

描述
将iterable中的元素作为function函数的输入,然后将返回结果为真的元素构建一个新的迭代器。
如果function是None,则会假设它是一个身份函数,即iterable中所有返回假的元素会被移除。
请注意,filter(function, iterable)相当于一个生成器表达式,当 function 不是None时等效为:(item for item in iterable if function(item))
function是None时等效为:(item for item in iterable if item)
示例

from typing import *
a = [1, 2, 3, 4]
r = filter(lambda x: x - 2, a)
print(list(r))
print(type(r))
print(isinstance(r, Iterable))
print(isinstance(r, Iterator))
print(isinstance(r, Generator))

[1, 3, 4]
<class 'filter'>
True
True
False

4.9.5.5 len(iterable)

描述
返回可迭代对象中包含的元素的个数。
示例
>>> len('string')
>>> len([1, 2, 3])

6
3

4.9.5.6 map(function, iterable, ...)

描述
返回一个将 function 应用于 iterable 中每一项并输出其结果的迭代器。 如果传入了额外的 iterable 参数,function 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。 当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。
示例

from typing import *

a = [1, 2, 3, 4]
b = [2, 4, 6, 8]
r = map(lambda x, y: x + y, a, b)
print(list(r))
print(type(r))
print(isinstance(r, Iterable))
print(isinstance(r, Iterator))
print(isinstance(r, Generator))

[3, 6, 9, 12]
<class 'map'>
True
True
False

4.9.5.7 max(str)、min(str)

描述
返回字符串中编号最大(最小)的字符。
示例

s = 'pythonTian赛车'
print(min(s))
print(max(s))

T

4.9.5.8 sum(iterable, start=0)

描述
从 start 开始自左向右对 iterable 的项求和并返回总计值。 iterable 的项通常为数字,而 start 值则不允许为字符串。
实例

print(sum([1, 2, 3, 4]))
print(sum([1, 2, 3, 4], 3))

10
13

4.9.5.9 zip(*iterables, strict=False)

描述
在多个迭代器上并行迭代,从每个迭代器返回一个数据项组成元组。strict参数为True用来检查所有的可迭代对象的长度是否一致,如果不一致则触发 ValueError。为False时则不检查。
示例

for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
	print(item)

(1, 'sugar')
(2, 'spice')
(3, 'everything nice')

4.9.6 序列(seq)

序列一定是可迭代对象,而可迭代对象不一定是序列。对于可迭代对象,如果元素是有序的,则是序列。
序列是一种特殊的可迭代对象,序列中的元素不仅可迭代,而且有顺序,可以通过索引或切片的方式获取序列中的元素。
Python中有6中基本的数据类型,其中字符串、列表、元组都是序列。

4.9.6.1 序列的拼接和重复

序列可以进行拼接和重复以进行扩展。
使用'+'进行拼接
通过+可以将两种相同类型的序列进行拼接,并且拼接之后的数据类型是原来的类型。
>>> 'pyth' + 'on'

'python'

相邻的2个字符串会自动合并:
>>> 'pyt' 'hon'

'python'

>>> [1, 2, 3] + [4, 5, 6]

[1, 2, 3, 4, 5, 6]

>>> (1, 2, 3) + (4, 5, 6)

(1, 2, 3, 4, 5, 6)

使用'*'进行重复
通过*可以将两种相同类型的序列进行重复,并且重复之后的数据类型是原来的类型。

>>> 'egg' * 3

'eggeggegg'

>>> [1, 2] * 2

[1, 2, 1, 2]

>>> (1, 2) * 2

(1, 2, 1, 2)

4.9.6.2 序列索引

序列支持索引(下标访问),第一个字符的索引是 0。之后每个元素的索引依次增加1。
>>> seq = 'Python'
>>> seq[0]
>>> seq[5]

'P'
'n'

索引还支持负数,用负数索引时,从右边开始计数:
>>> seq = 'Python'
>>> seq[-1]
>>> seq[-2]
>>> seq[-6]

'n'
'o'
'P'

注意,-0 和 0 一样,因此,负数索引从 -1 开始。
索引位置参考下图:
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5
-6 -5 -4 -3 -2 -1

索引越界会报错:
>>> seq = 'Python'
>>> seq[42]

Traceback (most recent call last):
File "", line 1, in
IndexError: string index out of range

注意:上述用字符串做演示,实际上列表和元组也是一样的

4.9.6.3 序列切片

除了索引,序列还支持切片。索引可以提取单个元素,切片则提取多个元素,参考下图:
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
注意:上面的数字对应的是每个字符中间的分隔'|'线。
序列的切片语法格式如下:
序列[头下标:尾下标]
>>> seq = 'Python'
>>> seq[0:2]
>>> seq[2:5]

'Py'
'tho'

切片索引的默认值很有用;省略开始索引时,默认值为 0,省略结束索引时,默认为到字符串的结尾:
>>> seq = 'Python'
>>> seq[:2]
>>> seq[4:]
>>> seq[-2:]

'Py'
'on'
'on'

注意,输出结果包含切片开始,但不包含切片结束。因此,seq[:i] + seq[i:] 总是等于 seq:
>>> seq = 'Python'
>>> seq[:2] + string[2:]
>>> seq[:4] + string[4:]

'Python'
'Python'

对于使用非负索引的切片,如果两个索引都不越界,切片长度就是起止索引之差。例如, seq[1:3] 的长度是 2。

切片会自动处理越界索引:
>>> seq = 'Python'
>>> seq[4:42]
>>> seq[42:]

'on'
''

指定步长的切片
>>> seq = 'Python'
>>> seq[0:6:2] # 从第0个元素到第5个元素,每隔2个就提取一个出来

'Pto'

>>> seq[:] # 浅拷贝,结果同string

'Python'

>>> seq[::-1] # 翻转整个字符串

'nohtyP'

步长为负数时,先翻转然后再每隔步长个字符就提取一个出来。

注意:
1、上述用字符串做演示,实际上列表和元组也是一样的
2、字符串切片后返回的是字符串,同理,列表、元组切片后返回的也是列表、元组。

4.9.6.4 序列方法

4.9.6.4.1 count(x)

描述
返回str在seq里面出现的次数。未找到则返回0
示例

seq = ' Python tian \t mao \n taobao '
print(seq.count('o'))
print(seq.count('ao'))
print(seq.count('io'))

4
1
0

4.9.6.4.2 index(x)、rindex(x)

描述
返回str在seq里面出现的索引。未找到则报错。rindex表示从右边开始查找。
示例

seq = ' Python tian \t mao \n taobao '
print(seq.index('o'))
print(seq.index('ao'))
print(seq.index('io'))

5
16
Traceback (most recent call last):
File "E:\studypy\tmp.py", line 4, in
print(s.index('io'))
ValueError: substring not found

4.9.6.5 序列函数

4.9.6.5.1 reversed(seq)

描述
返回序列的反向序列的迭代器。经过测试,对可迭代对象也生效。
示例

lst = [1, 2, 4]
re = reversed(lst)
print(re)
print(lst)
print(list(re))

<list_reverseiterator object at 0x0000021BE7523FA0>
[1, 2, 4]
[4, 2, 1]

4.9.6.5.2 sorted(iterable, /, *, key=None, reverse=False)

描述
根据 iterable 中的项返回一个新的已排序列表。具有两个可选参数,它们都必须指定为关键字参数。
key 指定带有单个参数的函数,用于从 iterable 的每个元素中提取用于比较的键 (例如 key=str.lower)。 默认值为 None (直接比较元素)。
reverse 为一个布尔值。 如果设为 True,则每个列表元素将按反向顺序比较进行排序。
示例1:简单排序
>>> sorted([5, 2, 3, 1, 4])

[1, 2, 3, 4, 5]

示例2:对元素调用函数排序
>>> sorted("This is a test string from Andrew".split(), key=str.lower)

['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']

示例3:将元素的某个索引作为key进行排序

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

r = sorted(student_tuples, key=lambda x: x[2], reverse=True)
print(r)

[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

多个关键字的排序

student_tuples = [
    ('john', 'A', 15),
    ('john', 'A', 20),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
    ('dave', 'B', 5),
]

r = sorted(student_tuples, key=lambda x: (x[0], x[2]))
print(r)

[('dave', 'B', 5), ('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15), ('john', 'A', 20)]

更多排序的内容可以参照:
Operator 模块排序

posted @ 2022-09-17 10:04  crleep  阅读(210)  评论(0编辑  收藏  举报