Python推导式、迭代器与生成器

发布时间 2023-03-24 22:47:06作者: XuShuo_Self

列表推导式

  • [表达式 for 局部变量名 in 可迭代对象]
  • [表达式 for 局部变量名 in 可迭代对象 if 条件]

如:

print([i**2 for i in range(1,10)])
# [1, 4, 9, 16, 25, 36, 49, 64, 81]
# 生成1~9的平方存储进列表后打印此列表

print([i**2 for i in range(1,10) if i>5])
# [36, 49, 64, 81]
# 生成1~9的平方,如果i大于5,则存储进列表,然后打印此列表
# (即生成6~9的平方存储进列表后打印此列表)

字典推导式

  • {键:值 for 局部变量名 in 可迭代对象 }
  • {键:值 for 局部变量名 in 可迭代对象 if 条件 }

如:

print({i:i**2 for i in range(1,10)})
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
# 生成1~9对应1~9平方的键值对存储进字典后打印此列表

print([i**2 for i in range(1,10) if i>5])
# {6: 36, 7: 49, 8: 64, 9: 81}
# 生成1~9对应1~9平方的键值对,如果i大于5,则存储进字典,然后打印此字典
# (即生成6~9对应6~9平方的键值对存储进字典后打印此字典)

集合推导式

  • {表达式 for 局部变量名 in 可迭代对象}
  • {表达式 for 局部变量名 in 可迭代对象 if 条件}

如:

print({i**2 for i in range(1,10)})
# {64, 1, 4, 36, 9, 16, 49, 81, 25}
# 生成1~9的平方存储进集合后打印此集合

print({i**2 for i in range(1,10) if i>5})
# {64, 49, 36, 81}
# 生成1~9的平方,如果i大于5,则存储进集合,然后打印此集合
# (即生成6~9的集合存储进集合后打印此集合)

元组推导式

元组推导式和另外三种推导式有一点点不同。。。由于其返回生成器对象,所以它也叫生成器表达式(迭代器的优势:相比较于列表推导式等更节约资源)。但我们可以用tuple(或list、set等)让它成为一个名副其实的“元组推导式”:

t = (i for i in range(3))
# 普通元组推导式

t = tuple(t)
#真·“元组推导式”
  • (表达式 for 局部变量名 in 可迭代对象)
  • (表达式 for 局部变量名 in 可迭代对象 if 条件)

如:

for j in (i**2 for i in range(1,10)):
    print(j, end='  ')
# 1  4  9  16  25  36  49  64  81
# 生成1~9的平方存储进生成器对象后遍历打印

for j in (i**2 for i in range(1,10) if i>5):
    print(j, end='  ')
# 36  49  64  81
# 生成1~9的平方,如果i大于5,则存储进生成器对象,然后遍历打印
# (即生成6~9的平方存储进生成器对象后遍历打印)

推导式与双分支结构紧凑格式

我们可以将双分支结构的if-else紧凑格式与推导式搭配使用,以搭配列表推导式为例:

  • [表达式1 if 条件 else 表达式2 for 局部变量名 in 可迭代对象]
print([i**2 if i!=9 else 0 for i in range(1,10)])
# 生成1~9的平方存储进列表,如果i不是9,则正常存储进列表
# 如果此时i是9,则将当前i替换成0存储进列表,然后打印此列表

print([i**2 if i!=9 else 0 for i in range(1,10) if i>5])
# [36, 49, 64, 0]
# 生成1~9的平方存储进列表,如果i大于5且i不是9,则正常存储进列表
# 如果此时i是9,则将当前i替换成0存储进列表,然后打印此列表
# (即生成6~9的平方存储进列表,再把i=9时的结果81替换成0后打印此列表)

迭代器

  • 迭代器协议是指实现了__next__魔法方法的类。
  • 只要是遵循迭代器协议并实现__iter__魔法方法的对象都可以称作“可迭代对象”。
  • Python部分关键词(如for等)和内建函数(如sum等)都遵循迭代器协议。
  • 迭代器在无可迭代对象后会触发StopIteration异常。
  • 我们也可以通过调用内建函数iter()来创建迭代器,其实质是调用其类或对象的__iter__方法。
  • 迭代器相对普通类型更省内存。
  • 迭代器只能向后迭代,不能向前迭代。

如下,我们可以制作一个调用计数器:

class MyClass():
    counter = 0
    def __iter__(self):
       print('调用了__iter__方法!')
       return self
    def __next__(self):
        print('调用了__next__方法!')
        self.counter += 1

my_class_obj = MyClass()
next(my_class_obj)
next(my_class_obj)
next(my_class_obj)
next(my_class_obj)
print(my_class_obj.counter)

它的结果是:

调用了__next__方法!
调用了__next__方法!
调用了__next__方法!
调用了__next__方法!
4

生成器

生成器是使用yield关键词的函数。每一个生成器都是迭代器,但迭代器不一定是生成器。可以说,生成器是简化了的迭代器。

yield与return相似,但与return不同的一点是,函数会在下一次使用next函数的时候返回下一个yield结果,直到触发StopIteration异常,如:

def func():
    yield 1
    yield 2
f = func()
print(next(f))
print(next(f))
print(next(f))

其结果是:

1
2
Traceback (most recent call last):
  File "*", line *, in *
    print(next(f))
StopIteration

由上,我们可以创建一个上限为10的计数器:

def func():
    counter = 0
    for i in range(10):
        counter += 1
        yield counter

只要next方法调用次数不超过10,就不会触发StopIteration异常。

实际yield还有一种派生用法:yield from 可迭代对象,会每次自动返回其遍历可迭代对象的当前值,如下两个函数的用法是完全相同的:

def func1():
    for i in range(5):
        yield i

def func2():
    yield from range(5)