python 迭代器与生成器及编码规范

可迭代对象

我们已经知道可以对list、tuple、dict、set、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

一个具备了__iter__方法的对象,就是一个可迭代对象。
可迭代的对象:使用iter内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。如果没有实现__iter__而实现了__getitem__方法,并且其参数是从零开始的索引,这种对象如序列也是可迭代的。

当解释器需要迭代对象时,会自动调用iter(x):
若对象实现了__iter__方法,获取一个迭代器;
若对象没实现__iter__方法,但实现了__getitem__方法,Python会创建一个迭代器,尝试从索引0开始获取元素;
若尝试失败,Python抛出TypeError。
Python 从可迭代的对象中获取迭代器

比如我们在对一个列表进行迭代时,如下代码:

x = [1,2,3]
for i in x:
    print(i)

实际执行情况如下图:
在这里插入图片描述
调用可迭代对象的__iter__方法返回一个迭代器对象(iterator)
不断调用迭代器的__next__方法返回元素
知道迭代完成后,处理StopIteration异常

iter方法从我们自己创建的迭代器类中获取迭代器,而getitem方法是python内部自动创建迭代器。
class Eg1:
    def __init__(self,text):
        self.text=text
        self.sub_text=text.split(' ')

    def __getitem__(self,index):
        return self.sub_text[index]

a=Eg1('1 2 3 4 5')
for i in a:
    print(i,end=' ')
>>> 1 2 3 4 5
注意:

检查对象能否迭代最准确的方法是调用iter(x)函数,如果不可迭代再处理TypeError。因为iter(x)会考虑到实现__getitem__方法的部分可迭代对象。

推导式

推导式分为 列表推导式、字典推导式、集合推导式等。在这里我们主要说其中一种也是用的最多列表推导式
列表推导式是Python构建列表(list)的一种快捷方式,可以使用简洁的代码就创建出一个列表,简单理解就是通过一个可迭代对象来构建出一个列表

语法

[表达式 for 变量 in 旧列表]
[表达式 for 变量 in 旧列表 if 条件]

实例
list1 = [1,2,3,4,5]
list2 = [i*2 for i in list1]
print(list2)
>>> [2,4,6,8,10]

# 多个for循环嵌套
list1=[1,2,3,4,5]
list2 = [(i,j) for i in list1 for j in list1]
print(list2)

# 嵌套列表推导式
list1=[1,2,3,4,5]
list2 = [[i,j for j in list1] for i in list1]
print(list2)
迭代器

可迭代对象,迭代器,生成器三者关系对应图
在这里插入图片描述

迭代器概念

那么什么叫迭代器呢?它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。

迭代器是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完结束。


##### 迭代器和可迭代对象之辨析:
联系:Python通过iter()函数从可迭代的对象中获取迭代器
区别:可迭代的对象不一定是迭代器,迭代器一定可迭代


把一个类作为一个迭代器使用需要在类中实现两个方法
__iter__,返回self,即迭代器本身。
__next__,返回下一个可用的元素。当没有元素时抛出StopIteration异常。
迭代器特点:

可迭代。由于Python中的迭代器实现了__iter__方法,因此也可以迭代。
易耗损。迭代器经过一次逐次取值的循环后便耗尽了。若想再次迭代须重建迭代器

迭代器实例
# 斐波那契数列

class Fib():
    def __init__(self,max):
        self.n = 0
        self.prev = 0
        self.curr = 1
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.n < self.max:
            self.prev,self.curr = self.curr,self.prev+self.curr
            self.n += 1
            return self.prev
        else:
            raise StopIteration

# 调用
f = Fib(5)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
# 再调用next()函数,会触发StopIteration

迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。直到无元素可调用,返回StopIteration异常。

迭代器模式

扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)

生成器
生成器概念

生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字,自动实现了“迭代器协议”(即__iter__和__next__方法),不需要再手动实现两方法。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。

生成器在迭代的过程中可以改变当前迭代值(.send(x)),而修改普通迭代器的当前迭代值往往会发生异常,影响程序的执行。

特点

生成器特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行生成器函数时返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

创建生成器的方式
通过生成器表达式的方式
g = (x * 3 for x in range(10))

通过函数的方式
def fu():
    n = 0
    while True:
        n += 1
        yield n
具有yield关键字的函数都是生成器,yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

注意:yield后面可以加多个数值(可以是任意类型),但返回的值是元组类型的。
生成器函数工作原理
def gen():
    print("start")
    yield "Jack"
    print("continue")
    yield "Dennis"
    print("end")
for i in gen():
    print("Hello ",i)

不难发现,for循环中操作的对象是生成器中的yield生成的值。原因在于,生成器会生成传给yield关键字的表达式的值

注意:不管有没有return语句,生成器函数都不会抛出StopIteration异常,而是在生成全部值之后直接退出

生成器还有一个send方法,可以往生成器里的变量传值,如下代码:
def foo():
    print("first")
    count=yield
    print(count)
    yield

f = foo()
# 使用send()函数时,刚启动的生成器必须先传一个None
f.send(None)
f.send(2)


调用过程:

f = foo()返回一个生成器
f.send(None)进入函数执行代码,遇到count=yield,冻结并跳出函数体
f.send(2)再次进入函数体,接着冻结的代码继续执行,把2传给变量count,打印count,遇到yield冻结并跳出函数
PEP8规范编写代码

PEP8: Python代码风格指南,PEP8 提供了 Python 代码的编写约定.
旨在提高代码的可读性, 并使其在各种 Python 代码中编写风格保持一致
风格一致性是非常重要的。更重要的是项目的风格一致性。在一个模块或函数的风格一致性是最重要的。

然而,应该知道什么时候应该不一致,有时候编码规范的建议并不适用。当存在模棱两可的情况时,使用自己的判断。看看其他的示例再决定哪一种是最好的,不要羞于发问。

特别是不要为了遵守PEP约定而破坏兼容性!

几个很好的理由去忽略特定的规则:

当遵循这份指南之后代码的可读性变差,甚至是遵循PEP规范的人也觉得可读性差。
与周围的代码保持一致(也可能出于历史原因),尽管这也是清理他人混乱(真正的Xtreme Programming风格)的一个机会。
有问题的代码出现在发现编码规范之前,而且也没有充足的理由去修改他们。
当代码需要兼容不支持编码规范建议的老版本Python。
1. 缩进使用4个空格, 空格是首选的缩进方式. Python3 不允许混合使用制表符和空格来缩进
2. 每一行最大长度限制在79个字符以内
3. 顶层函数、类的定义, 前后使用两个空行隔开

import 导入
导入建议在不同的行, 例如:
import os
import sys
不建议如下导包
import os, sys
但是可以如下:
from subprocess import Popen, PIPE

导包位于文件顶部, 在模块注释、文档字符串之后, 全局变量、常量之前. 导入按照以下顺序分组:
标准库导入
相关第三方导入
本地应用/库导入
在每一组导入之间加入空行

Python 中定义字符串使用双引号、单引号是相同的, 尽量保持使用同一方式定义字符串. 当一个字符串包含单引号或者双引号时, 在最外层使用不同的符号来避免使用反斜杠转义, 从而提高可读性

表达式和语句中的空格:
避免在小括号、方括号、花括号后跟空格
避免在逗号、分好、冒号之前添加空格
冒号在切片中就像二元运算符, 两边要有相同数量的空格. 如果某个切片参数省略, 空格也省略
避免为了和另外一个赋值语句对齐, 在赋值运算符附加多个空格
避免在表达式尾部添加空格, 因为尾部空格通常看不见, 会产生混乱
总是在二元运算符两边加一个空格, 赋值(=),增量赋值(+=,-=),比较(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),布尔(and, or, not)
避免将小的代码块和 if/for/while 放在同一行, 要避免代码行太长
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()

永远不要使用字母 'l'(小写的L), 'O'(大写的O), 或者 'I'(大写的I) 作为单字符变量名. 在有些字体里, 这些字符无法和数字0和1区分, 如果想用 'l', 用 'L' 代替

类名一般使用首字母大写的约定

函数名应该小写, 如果想提高可读性可以用下划线分隔

如果函数的参数名和已有的关键词冲突, 在最后加单一下划线比缩写或随意拼写更好. 因此 class_ 比 clss 更好.(也许最好用同义词来避免这种冲突)

方法名和实例变量使用下划线分割的小写单词, 以提高可读性

关于代码规范的例子,可以去看https://blog.csdn.net/ratsniper/article/details/78954852

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值