目录

  • 可迭代对象iterable
  • 字面意思:
  • 专业角度:
  • 优点:
  • 缺点:
  • 迭代器对象iterator
  • 字面角度:
  • 专业角度:
  • 优点:
  • 缺点:
  • 使用while循环模拟for循环对迭代器取值
  • 可迭代对象转换为迭代器对象
  • for 循环工作原理
  • 内置函数dir()
  • 生成器generator
  • 获取生成器的两种方式:
  • 1、yield构建生成器函数。
  • 2、生成器表达式。
  • 字典推导式
  • 集合推导式:
  • 表达式应用:


可迭代对象iterable

字面意思:

  • 可迭代:更新迭代。迭代是一个重复的过程,每次重复是基于上一次的结果而继续的,每次都有新的内容。
  • 可迭代对象:可以进行循环更新的一个实实在在的值。

专业角度:

内部含有'__iter__'方法的对象。

str.__iter__
list.__iter__
set.__iter__
dict.__iter__
tuple.__iter__
优点:

1、存储的数据能直接显示,比较直观。

2、拥有的方法比较多,操作方便。

缺点:

1、占内存,有多少值就会占用多少内存。

2、只能通过索引,key取值。

for循环能进行取值是因为在底层做了转换,将可迭代对象转换成迭代器,然后取值。



迭代器对象iterator

字面角度:

迭代器指的是迭代取值的工具(数据结构)。

专业角度:

内置有__next__方法和__iter__方法的对象。

f = open('a.txt',mode'rt',encoding='utf-8')
print('__iter__' in dir(f),'__next__' in dir(f))
f.close()
True True

文件对象就是迭代器,这是为了避免当一个文件过大时,占用过多内存,所以Python做的优化。


优点:

1、提供一种不依赖索引的通用迭代取值方案。

2、惰性计算,节省内存。每次在内存中仅存在一个值。

缺点:
  • 1、速度慢,以时间换空间。
  • 2、取值麻烦。

先将可迭代对象调用__iter__方法或iter()函数转换成迭代器,然后调用__next__方法或next()函数取值。

li = [11,22,33]
li_obj = li.__iter__()
print(li_obj.__next__())
print(li_obj.__next__())
print(li_obj.__next__())
print(li_obj.__next__())

每调用一次__next__就会取一次值,当值都取完后,再调用__next__会抛StopIteration异常。

StopIteration
11
22
33
  • 3、没有__len__方法,无法预测值的长度,只能取到抛出异常才知道已经取完。
s1 = {11,22,33}
obj = iter(s1)
print('__len__' in dir(obj))

False
  • 4、一次性取值。
li = [11,22,33]
li_obj = iter(li)
for i in li_obj:
    print(i)

print('====分隔符====')
for i in li_obj:
    print(i)

在第一个for循环取完值后,第二次for循环已经取不出来值了。所以结果为:

11
22
33
====分隔符====

除非重新调用iter()转换成迭代器,就可以重新取值。

li_obj2 = iter(li)
for i in li_obj:
    print(i)

11
22
33
====分隔符====
11
22
33

使用while循环模拟for循环对迭代器取值

1、调用可迭代对象.__iter__方法,得到一个迭代器对象。

2、迭代器调用next(),获取一个值。

3、循环取值,直到抛出StopIteration异常,捕捉异常然后结束循环。

s1 = {11,22,33}
obj = iter(s1)
while 1:
    try:
        print(next(obj))
    except StopIteration:  # 当捕捉到StopIteration异常时就break结束循环。
        break

将可迭代对象转换为迭代器就可以实现不依赖索引或key也能取值的目的。


可迭代对象转换为迭代器对象

可迭代对象调用__iter__方法或iter()函数会转换成迭代器。每次转换得到的都是一个新的迭代器。

li = [11,22,33]
li_obj = iter(li)
li_obj2 = iter(li)
print(li_obj is li_obj2)  # 两次都得到一个新的迭代器。
False

两个迭代器都可以取值。

for i in li_obj:
    print(i)

for i in li_obj2:
    print(i)
    
11
22
33
11
22
33

如果是迭代器对象调用__iter__方法或iter()函数,所得到的结果为迭代器对象本身。这么设计是为了方便for循环的工作机制,使迭代器对象无论调用__iter__方法多少次,得到的结果都是迭代器对象自身。

li = [11,22,33]
li_obj = iter(li)
li_obj2 = li_obj.__iter__()
li_obj3 = li_obj2.__iter__().__iter__()
print(li_obj is li_obj2 is li_obj3)

# 无论调用多少次,结果还是迭代器自身。
True

for 循环工作原理

1、调用可迭代对象.__iter__方法,得到一个迭代器对象。

2、调用next(迭代器),将返回值赋值给in前面的变量。

3、循环取值,直到抛出StopIteration异常,for就会捕捉异常然后结束循环。

s1 = {11,22,33}
for i in s1:
    print(i)
    
33
11
22

由于迭代器对象无论调用__iter__方法多少次,得到的结果都是迭代器对象自身,所以for循环无论迭代的是可迭代对象还是一个迭代器对象,都可以直接调用该对象的__iter__方法,简化了for循环的设计思路。


内置函数dir()

获取一个对象的所有内置方法,以列表的形式返回。

l = [1,2,3]
print(dir(l))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

判断一个对象是否是可迭代对象。

s = 'abc'
print('__iter__' in dir(s))

True

生成器generator

生成器的本质就是迭代器。

区别:

生成器是我们自己用Python代码构建的数据结构。迭代器是Python提供的,或者调用iter()转换得来的。

def f():
    yield 10

print('__iter__' in dir(f()) and '__next__' in dir(f()))
True

获取生成器的两种方式:

1、yield构建生成器函数。

2、生成器表达式。


1、yield构建生成器函数。

函数体内但凡出现yield关键字,调用函数将不会触发函数体代码的运行,而是会返回一个生成器对象。

def func():
    print(111)
    yield 1
    print(222)
    yield 3
ret = func()
print(ret)
print(next(ret))

<generator object func at 0x0000000002338B30>
111
1

特点:

  • 只要函数中有yield那么就是生成器函数。
  • 生成器函数加括号()并不会直接执行函数体代码,而是返回一个生成器对象。
def func():
    print(111)
    yield 'aaa'
obj = func()
print(obj)

# 并不会打印111
<generator object func at 0x00000000025487B0>
  • 每调用一次生成器函数都会获得一个新的生成器。
def func():
    print(111)
    yield 'aaa'
obj = func()
obj2 = func()  # 会得到一个新的生成器.
print(obj is obj2)

False
  • 函数中可以存在多个yield,并且yield不会终止函数。
  • 通过next取出yield返回的值,并会保留上次取值的位置。一个next对应一个yield,多了会报错。
    也就是说,yield可以暂停函数,然后我们可以拿到返回值进行处理后,用next方法再次触发函数代码的运行,协程方面有应用。

return与yield的区别:

相同点:在返回值反面用法一样。

不同点:yield可以返回多次值,而return只能返回一次。


用生成器函数手撸个range功能。

def my_range(start,stop,step=1):
    while start < stop:
        yield start
        start += step
        
for i in my_range(0,10):
    print(i)

0
2
4
6
8

用生成器函数手撸个无限取值的功能。

def get_num():
    num = 0
    while 1:
        yield num
        num += 1

obj = get_num()
print(next(obj))
print(next(obj))
print(next(obj))


结果为:
0
1
2

每调用一次生成器函数都会获得一个新的生成器,如果用下述写法,每次得到的是个新生成器,永远是取0:

def get_num():
    num = 0
    while 1:
        yield num
        num += 1

print(next(get_num()))
print(next(get_num()))

0
0

2、生成器表达式。

首先讲列表推导式:能用一行代码构建一个比较复杂有规律的列表。

格式:

1、循环模式:

l = [表达式 for 变量 in iterable]

# 多层嵌套
l = [表达式 for 变量 in iterable for 变量 in iterable ...]

2、筛选模式:

l = [表达式 for 变量 in iterable if 条件 for 变量 in iterable]

超过三层循环才能构建成功的,不建议使用列表推导式。查找错误(debug模式)不行。

# 常规创建
li = []
for i in range(1,10):
    if i > 3:
		li.append(i)
    
# [4, 5, 6, 7, 8, 9]

# 列表推导式:
li = [i for i in range(1,10) if i > 3]
print(li)

# [4, 5, 6, 7, 8, 9]

生成器表达式与列表推导式的写法几乎一模一样。将列表推导式的方括号改为圆括号。也有筛选模式,循环模式,多层嵌套。

实例:

l = (i for i in range(10))
print(next(l))
print(next(l))
print(next(l))

0
1
2

字典推导式:

l1 = ['jay','jj','meet']
l2 = ['周杰伦','林俊杰','元宝']
dic = {l1[i]:l2[i] for i in range(len(l1))}
print(dic)

# {'jay': '周杰伦', 'jj': '林俊杰', 'meet': '元宝'}

items = [('a',1),('b',2),('c',3)]
dic = {key:value for key,value in items if value > 1 }
print(dic)

# {'b': 2, 'c': 3}

集合推导式:

s1 = {i for i in range(1,4)}
print(s1)
{1, 2, 3}

并没有元组推导式,因为有列表推导式就可以使用tuple()将列表转换成元组。

li = tuple([i for i in range(1,10) if i > 3])
print(li)

(4, 5, 6, 7, 8, 9)

表达式应用:

计算一个文件内有多少个字符。

with open('user.txt',mode='r',encoding='utf-8') as f:
# 常规方法,读取每一行计算字符数,然后再累加
    d = 0
    for line in f:
        d += len(line)
    print(d)  

# 可使用列表推导式简化,但文件行数若过多,会造成列表元素过多,占用大量内存空间
    d = sum([len(line) for line in f])
    print(d)

# 使用生成器表达式,每次只会占用一块内存空间
    d = sum((len(line) for line in f))
    print(d)


# 函数的括号和生成器表达式的括号可以合并。
    d = sum(len(line) for line in f)
    print(d)