可迭代对象
我们已经知道可以对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