读书笔记:Python 学习手册(2)
结于2021-08-04;
OREILY的书籍,可读性很强,入门类,而且这本书很厚;
- 第三部分 语句和语法
- 第四部分 函数
第三部分 语句和语法 —— 第10章 Python语句简介
Python是面向过程的、基于语句的语言;
python语句:
# 赋值语句、调用语句、打印
# if/elif/else for while pass break continue def return
# yield:生成器函数
def gen(n):
for i in n:
yield i * 2
# global:命名空间
x = 'old'
def functionA():
global x, y
x = 'new'
# nonlocal:3.0+ 命名空间
def outer():
x = 'old'
def inner():
nonlocal x
x = 'new'
# import from try/except/finally raise assert
assert X > y, 'X too small'
# with/as:环境管理器
# del:删除引用
复合语句的一般形式:
- 首行以冒号结尾,下一行嵌套的代码进行格式缩进;
- 一行的结束就是终止该行语句,无须分号;
- 一行中的多条语句,需用分号隔开,分号作为语句界定符;
- 使用一对括号,可以让语句横跨多行,包括使用 小括号 中括号 大括号;
一个简单的交互式循环:
while True:
reply = input('Enter text:')
if reply == 'stop': break
print(reply.upper())
while True:
reply = input("Enter text:")
if reply == 'stop':
break
try:
num = int(reply)
except:
print('Bad!' * 8)
else:
print(int(reply) ** 2)
print('Bye')
第11章 赋值、表达式和打印
- 赋值语句建立对象引用值
- 变量名在首次赋值时会被创建
- 变量名在应用前必须先赋值
赋值语句:
[a,b] = ['a','b'] # 列表赋值
a,b,c,d = 'spam' # 序列赋值
a, *b, c = 'spam' # 拓展的序列解包赋值
a, b, *c = 'spam' # 注意:一个序列解包赋值 总是返回多个匹配项的列表,这里c将会是一个列表[m]
a,b,c,*d = 'spam' # d=[]
a,b = b,a # 元组实现交换值,(临时组件的元组 储存原始值)
for ((a,b),c) in [((1,2),3),((4,5),6)]
a,b,c
a = b = c = 'spam' # 多目标赋值语句
# 增强赋值语句
X += Y
X **= Y
# ...
拓展的序列解包赋值 相比分片语法简单,应用更广泛;
Python中没有 自增 自减运算符;
Python的变量命名是区分大小写的;
Python中变量命名惯例:
- 以单一下划线开头的变量名(
_X
)不会被from module import *
语句导入; - 前后有下划线的变量名(
__X__
)是系统定义的变量名,对解释器有特殊意义; - 仅以两个下划线开头(
__X
)是类的本地变量;
Python中并没有所谓的C++的const声明的概念,有些对象是不可变的,但变量名走势可以赋值的;
打印操作
print语句实现打印:只是对程序员友好的标准输出流的接口而已;
Python中,打印与文件和流的概念紧密相连;
- 文件写入方法是把字符串写入到任何的文件;
- print默认地把对象打印到stdout流,添加了一些自动的格式化;
- 与文件方法不同,使用打印操作时,不需要把对象转换成字符串;
脚本启动时创建的3中数据连接:标准输入流、标准输出流、错物流;
- 标准输出流 可以作为内置的sys模块中的stdout文件对象使用;
- print更容易使用,把文本打印到其他文件或流;
# print
print([object, ...][, sep=''][, end='\n'][, file=sys.stdout])
# 等价的sys.stdout
import sys
sys.stdout.write("Hello world\n")
print(X,Y)
# 等价于
sys.stdout.write(str(X) + ' ' + str(Y) + '\n')
# set end file 关键字参数
print(x,y,z, file=open('data.txt', 'w'))
print(open('data.txt').read())
# file.close()
file参数:带有一个类似文件的writer(string)
方法的任何
对象都可以传递;如果是文件的话应该已经为了输出而打开;
重定向输出流:
import sys
temp = sys.stdout
sys.stdout = open('log.txt', 'a') # 重定向打印到一个文件
# ...
print(x,y,x) # 打印到log.txt
sys.stdout.close()
sys.stdout = temp # 重新赋值 ,也可以使用sys模块的__stdout__属性,指的就是程序启动时sys.stdout的原始值;
错误消息打印到标准错误流sys.stderr
在操作系统命令行中对Python脚本的输入和输出进行像往常一样的重定向:
python script.py < inputfile > outputfile
- Python的打印操作重定向工具实质是这些shell语法形式的纯Python替代;
第12章 if测试和语法规则
短路计算,以及and和or逻辑运算表达式返回的始终是对象:
2 or 3 , 3 or 2 # (2,3)
2 and 3, 3 and 2 # (3,2)
if/else
三元表达式:
A = Y if X else X
第13章 while和for循环
- 常用循环:while for
- 循环中语句:break continue
- 循环中常用内置函数:range zip map enumerate
- 奇特的迭代工具(后续):生成器 filter reduce
while <test>:
pass
else:
<循环全部正常离开时,执行;如果遇到break将不会执行else>
for循环在Python中是一个通用的序列迭代器:可以遍历任何有序列表对象内的元素;
- 内置range函数返回一系列连续增加的整数,可以作为for中的索引;
- 内置zip函数返回并行元素的元素的列表,可用于在for内遍历数个序列;
for <target> in <object>:
pass
else:
<循环全部正常离开时,执行;如果遇到break将不会执行else>
for key in D:
pass
for (key, value) in D.items():
pass
list(range(-5, 5)) # [-5,...,4]
list(range(5, -5, -1)) # [5,...,-4]
for i in range(0, len(S), 2)): # 非完备遍历
pass
for c in S[::2]:
pass
# 遍历的同时修改列表
for i in range(len(L)):
L[i] += 1
list(zip(L1, L2))
# zip可以接受任何类型的序列 (包括文件),同时支持两个以上的序列;
for (x, y) in zip(L1, L2):
pass
# map是Python3.0中的一个值生成器,因此必须传递给list以一次性收集其结果
list(map(ord, 'spam')) # [115, 112, 97, 109]
# 使用zip构建字典
D = {}
for (k, v) in zip(keys, vals):
D[k] = v
# or
D = dict(zip(keys, vals))
产生偏移和元素:enumerate
- 单纯的for循环 无法得到index 和 value
- enumerate函数返回一个生成器对象(有一个
__next__
方法);
for (offset, item) in enumerate(S):
pass
第14章 迭代器和解析,第一部分
对Python中所有会从左至右扫描对象的迭代工具而言,for循环、列表解析、in成员关系测试、map函数等都可用与任何可迭代对象;
文件迭代器:
- 文件也有一个
__next__
方法,每次调用,返回文件中下一行; - 需要注意到达文件末尾时,
__next__
方法会引发一个内置的StopIteration
异常,而不是返回空字符串;
迭代器协议:
- 可调用的
__next__
方法,可被捕获的StopIteration
异常; - Python中,任何这类对象都认为是可迭代的;
- 读取这类对象的最好方式就是放在for循环中;
为了支持手动迭代,Python3.0还提供了一个内置函数next,它会自动调用一个对象的__next__
方法;iter内置函数:返回迭代器;
L = [1,2,3]
I = iter(L) # 调用iter启动迭代 这一步对于文件是不需要的,因为文件对象就是自己的迭代器
I.next()
字典有一个迭代器,在迭代环境中,会自动一次返回一个键:I = iter(D)
shelves:用于Python对象的一个根据键访问的文件系统;
os.popen():读取shell命令的输出的一个工具;
迭代协议也是我们必须把某些结果包装到一个list调用中以一次性看到它们的值的原因;可迭代的对象一次返回一个结果,而不是一个实际的列表;
R = range(5)
I = iter(R)
next(I)
list(range(5))
列表解析比手动的for循环语句运行的更快,因为它们是在解释器内部以C语言速度执行的;
在文件上使用列表解析:
lines = [line.rstrip() for line in open('script1.py')]
扩展的列表解析语法:
# 添加一条if过滤子句
lines = [line.rstrip() for line in open('script1.py') if line[0] == 'p']
# 多个for子句(这一一个嵌套结构)
[x + y for x in 'abc' for y in 'lmn']
map函数是一个内置函数,类似于列表将诶西,但是有局限性,它需要一个函数而不是一个任意的表达式;在Python3.0中,返回的是一个可迭代的对象自身;必须包含到一个list调用中以迫使其一次性返回所有值;
接收可迭代对象的内置函数:
sorted
排序可迭代对象中的各项,返回列表zip
组合可迭代对象中的各项,返回可迭代对象enumerate
根据相对位置来配对可迭代对象中的项,返回可迭代对象filter
选择一个函数为真的项,返回可迭代对象reduce
针对可迭代对象中的成对的项运行一个函数,返回一个值- 其他的:
sum any all max min
list(filter(bool, ['span', '', 'ni'])) # ['spam', 'ni']
字典视图迭代器:
- 字典的keys、values、items方法返回可迭代的视图对象;
# 排序字典键的方式
for k in sorted(list(D.keys())):
pass
for k in sorted(D):
pass
其他迭代器主题:(后续)
- 使用yield语句,用户定义的函数可以转换为可迭代的生成器函数
- 当编写在圆括号中的时候,列表解析转变为可迭代的生成器表达式
- 用户定义的类通过
__iter__
或__getitem__
运算符重载变得可迭代;
第15章 文档
Python包含可以使文档的编写变得更简单的语法和工具;
Python预制功能如 内置函数和异常、预定义的对象属性和方法、标准库模块等文档资源是了解这些工具的主要方式;
- 文档字符串(docstring)
- PyDoc系统;
形式 | 描述 |
---|---|
#注释 | 文件中文档 |
dir函数 | 对象可用属性 |
文档字符串 __doc__ | 对象文件中的文档 |
PyDoc:help函数 | 对象交互帮助 |
PyDoc:HTML报表 | 浏览器中的模块文档 |
标准手册及其他 |
文档字符串 __doc__
- 一类注释字符串,放在模块文件、函数以及类语句的顶端;
- Python会自动封装这个字符串,成为所谓的文档字符串,使其成为相应对象的
__doc__
属性;
"""
Module doc
"""
def funcA():
"""
func doc
"""
pass
class ClassA:
"""
class doc
"""
pass
查看的话只需导入这个模块,然后查看它的__doc__
属性;也可以通过路径访问:module.class.method.__doc__
;
help函数:
- 会启动PyDoc从而产生简单的文字报表;
HTML报表
标准手册集:
- IDLE的Help选项开启;
- 或在线阅读(http://www.python.org)Documentition链接;
导入模块时,不要使用扩展名或路径;
第四部分 函数 —— 第16章 函数基础
一个函数就是将一些语句集合在一起的部件;最大化的代码重用和最小化的代码冗余;
编写函数:
- def 表示可执行代码
- def 创建了一个对象并将其赋值给某一个变量名
- lambda 创建一个对象 但将其作为结果返回;
- return 将一个结果对象发送给调用者
- yield 向调用者发回一个结果对象;
- global
- nonlocal
- 函数通过赋值(对象引用)传递的;
- 参数、返回值以及变量 并不是声明
Python中的多态:
- 在Python中为对象编写接口,而不是数据类型;
第17章 作用域
关于所有变量名,包括作用域的定义在内,都是在Python赋值时生成的;
函数为程序增加了一层命名空间;
- 每个模块都是一个全局作用域;
- 全局作用域的作用范围仅限单个文件;
- 每次对函数的调用都创建了一个新的本地作用域
- 赋值的变量名 除非声明为全局变量 或 非本地变量,否则均为本地变量;
- 所有其他的变量名都可以归纳为本地、全局或者内置的;
内置作用域:
- 内置作用域仅仅是一个名为
builtins
的内置模块; - 由于变量名builtins本身并没有放入内置作用域,要使用的话还需导入;
- 根据LEGB查找流程,Python最后将自动搜索这个模块,并自动得到所有变量名;
import builtins
dir(builtins)
# 内置异常 内置函数 变量名
builtins.zip
由于LEGB查找流程,本地作用域 可能覆盖 全局和内置作用域,全局变量名可能覆盖内置的变量名;
global语句
global是一个命名空间的声明,它告诉Python函数打算生成一个或多个全局变量名;
global允许我们修改一个模块文件的顶层的一个def之外的名称;和随后的nonlocal几乎相同,只不过他是用于嵌套的def的本地作用域内的名称,而不是嵌套在模块中的名称;
def func():
# 注意 在函数运行前 X是不存在的
global X
X = 99
func()
print(X)
一些程序委任一个单个的模块文件去定义所有的全局便来能;在Python中使用多线程进行并行计算程序实际上是依靠全局变量的,因为全局变量在并行线程中 在不同的函数之间成为了共享内存,所以扮演着通信工具的角色;
Python标准库
_thread theading queue
,由于线程化函数都是在同一进程中执行,全局作用域往往充当他们之间的共享内存;
线程用于GUI中长时间运行的任务,以实现广泛地非阻塞的操作并利用CPU的能力;
每个模块都是自包含的命名空间,必须导入模块才能看到它内部的变量;这是关于模块的一个要点:通过在每个文件的基础上分隔变量,它们避免了跨文件的命名冲突;一个文件被导入后,它的全局作用域实际上就构成了一个对象的属性;
nonlocal
- 如果X在函数内部声明为全局变量,它将会创建或改变变量名为X为整个模块的作用域;
- 如果X在函数内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域中的名称X;
nonlocal与global的不同之处:
- nonlocal应用于一个嵌套函数作用域的一个名称,而不是def之外的全局作用域;
- 声明nonlocal名称的时候,它
必须
已经存在于该嵌套函数的作用域中且赋值过;
nonlocal即允许对嵌套的函数作用域中的名称赋值,并且把这样的名称的作用域查找限制在嵌套的def;(nonlocal限制作用域查找仅为嵌套的def)
nonlocal使得对该语句中列出的名称的查找从嵌套的def的作用域开始,而不是从声明函数的本地作用域开始,也就是说,nonlocal也意味着“完全略过我的本地作用域”;
实际上,当执行到nonlocal语句的时候,nonlocal中列出的名称必须在一个嵌套的def中提前定义过;
Python是在函数创建的时候解析nonlocal的,而不是在函数调用的时候;
def tester(start):
state = start
def nested(label):
nonlocal state
print(label, state)
state += 1
return nested
F = tester(0)
F('A') # A 0
F('B') # B 1
F('C') # C 2
# 每次调用tester工厂函数 都会在内存中获得其状态的一个副本;
# 嵌套作用域中的state对象基本上附加到了返回的nested函数对象,每次调用都产生一个新的、独特的state对象,更新一个函数的state不会影响到其他的;
一个可调用的类实现:
class tester:
def __init__(self, start):
self.state = start
def __call__(self, label):
print(label, self.state)
self.state += 1
H = tester(99)
H("A") # A 0
H('B') # B 1
闭合(closure)或工厂函数:一个能记住嵌套作用域的变量值的函数;
def maker(N):
def action(X):
return X ** N
return action
f = maker(2)
f(3) # 9
f(4) # 16
def func():
x = 4
action = (lambda n: x ** n)
return action
x = func()
print(x(2))
作用域 与 带有循环变量的默认参数相比较:
- 这是一个特例,如果函数嵌套在一个循环中,且引用了上层的循环变量,所有在这个循环中产生的函数将会有相同的值——在最后一次循环中完成时被引用变量的值;
- 因为嵌套作用域中的变量在嵌套的函数被调用时才进行查找;
def makeActions():
acts = []
for i in range(5):
acts.append(lambda x: i ** x) # All remember same last i
return acts
acts = makeActions()
acts[0]
# 解决方式是使用默认参数把当前值传入嵌套作用域;因为默认参数是在嵌套函数创建时平柜的,而不是在其稍后调用;
for ...
acts.append(lambda x, i=i: i ** x) # Remember current i
函数陷阱:
def f(a=[])
,默认值是以单一对象来实现的,可变默认值会在调用过程中保留状态,而不是每次调用时都重新设定初始值;
函数也是一个对象,有的时候也可以借助为函数的属性赋值实现状态的保持,它允许从嵌套的函数之外访问状态变量;
全局、非本地、类和函数属性 都提供了状态保持的选项;全局只支持共享的数据,类需要OOP的基本知识,类和函数属性都允许在嵌套函数自身之外访问状态;
尽量避免使用全局变量和跨文件间的修改;
第18章 参数
函数参数名 和 调用者作用域中的变量名是没有区别的;
- 不可变参数“通过值”进行传递
- 可变对象是通过“指针”进行传递的
参数匹配:
- 位置:从左到右进行匹配;
- 关键字参数:通过参数名进行匹配;调用时使用 name=value
- 默认参数:为没有传入值的参数定义参数值,定义时使用 name=value
- 可变参数:收集任意多个基于位置 或 关键字的参数,定义时使用,相应参数以字符
*
开头进行收集; - 可变参数解包:传递任意多个基于位置 或 关键字的参数,调用时使用,相应参数以字符
*
开头进行解包; - Keyword-only参数:参数必须按照名称传递;
def func(name)
func(value)
def func(name=value)
func(name=value)
def func(*name)
func(*sequence)
def func(**name)
func(**dict)
def func(*args,name) # 必须按照关键字传递
def func(*, name=value) # Python3 参数必须按照名称传递;
不同类型参数匹配的顺序:
- 函数调用中,必须以此顺序:任何位置参数,关键字参数,
*sequence
,**dict
; - 函数定义时,必须以此顺序:一般参数name,默认参数name=value,
*name
,name=value keyword-only等关键字参数,**name
;(重点)
Python内部是使用以下的步骤来在赋值前进行参数匹配的:
- 通过位置分配非关键字参数
- 通过匹配变量名分配关键字参数
- 其他额外的非关键字参数分配到
*name
元组中 - 其他额外的关键字参数分配到
**name
字典中 - 用默认值分配给在头部未得到分配的参数;
在Python3中,函数头部中的参数也可以有一个注解值,特定形式如
name:value=default
;函数自身也可以有一个注解值,以def f()->value
形式给出;(def add(x:int=0,y:int=0)->int:pass
)
强调一点,当关键字参数在调用过程中使用时,参数排列的位置并没有关系;
def tracer(func, *pargs, **kargs):
print('calling:', func.__name__)
return func(*pargs, **kargs)
def func(a,b,c,d):
return a+b+c+d
print(tracer(func, 1, 2, c=3, d=4))
Python3 Keyword-Only参数
keyword-only参数编码为命名的参数,出现在参数列表中的*args
之后;所有这些参数都必须在调用中使用关键字语法来传递;
# c必须只按照关键字传递
def kwonly(a, *b, c):
pass
kwonly(1,2,c=3) # 1 (2,) 3
kwonly(a=1, c=3) # 1 () 3
可以对keyword-only参数使用默认值;
注意:keyword-only参数必须在一个单个星号后面指定,而不是两个星号;命名参数不能出现在
**args
形式后,并且一个**
不能单独出现在参数列表中;
无论何时,一个参数名称出现在*args
之前,它可能是默认位置参数,而不是keyword-only参数;
求最小值的三种实现示例代码:
def min1(*args):
res = args[0]
for arg in args[1:]:
if arg < res:
res = arg
return res
def min2(first, *rest):
for arg in rest:
if arg < first:
first = arg
return first
def min3(*args):
tmp = list(args)
tmp.sort() # Python sort例程是C实现,采用高度优化算法,试着利用被排序元素之间的部分次序,这种排序称为 timsort
return tmp[0]
def minmax(test, *args):
res = args[0]
for arg in args[1:]:
if test(arg,res):
res = arg
return res
def lessthan(x, y): return x < y
def grtrthan(x, y): return x > y
minmax(lessthan, 1,2,3,4,5)
minmax(grtrthan, 1,2,3,4,5)
Python 有内置的min和max函数;
模拟Print函数:
import sys
def print30(*args, **kargs):
sep = kargs.get('sep', ' ')
end = kargs.get('end', '\n')
file = kargs.get('file', sys.stdout)
output = ''
first = True
for arg in args:
output += ('' if first else sep) + str(arg)
first = False
file.write(output + end)
print30(1,2,3 sep='??', end='.\n', file=sys.stderr)
# 使用Keyword-Only参数:
def print30(*args, sep=' ', end='\n', file=sys.stdout):
pass
# 捕获到外部的关键字参数
def print30(*args, **kargs):
sep = kargs.pop('sep', ' ')
end = kargs.pop('end', '\n')
file = kargs.pop('file', sys,stdout)
if kargs: raise TypeError('extra keywords: %s' % kargs)
output = ''
...
在Python3.0中,keyword-only参数可以简化一类既接收参数 又接收选项的函数;
第19章 函数的高级话题
- 递归函数
- 函数属性和注解
- lambda表达式(在GUI中十分常用)
- map和filter等函数式编程工具
递归:
def mysum(L):
return 0 if not L else L[0] + mysum(L[1:])
和for循环提供的自动迭代相比,递归在内存空间和执行时间方面效率较低;不需要在调用堆栈上针对每次迭代都有一个本地作用域的副本;
对于无法线性迭代的场景,可以应用递归;
def sumtree(L):
tot = 0
for x in L:
if not isinstance(x, list):
tot += x
else:
tot += sumtree(x)
return tot
sumtree([1,[2,[3,4],5],6,[7,8]])
函数对象:属性和注解
函数对象支持与调用无关的属性存储和注解;
在def运行之后,函数名直接就是一个对象的引用;
Python的通用对象模式和无须类型声明使得该编程语言有了令人惊讶的灵活性;
函数内省:
dir(func)
dir(func.__code__)
func.__code__.co_varnames
func.__code__.co_argcount
# 利用这些信息方便管理函数
函数属性:
- 可以向函数附加任意的用户定义的属性;
- 这样的属性可以直接把状态信息附加到函数对象;
- 模拟了其他语言中“静态本地变量”,对于一个函数来说是本地的;值在函数退出后仍保留;属性与对象相关而非作用域;
Python3.0中的函数注解:
- 与函数的参数和结果相关
- 特殊语法支持:参数的冒号之后,返回值在
->
之后; - 可选
- 注解直接附加到函数对象的
__annotations__
属性
# 普通函数
def func(a, b, c):
pass
# 添加注解
def func(a:'aaa', b:(1,10), c:float) -> int:
pass
func.__annotations__ # 一个字典
注解可以用作参数类型或值的特定限制,并且较大的API可能使用这一功能作为注册函数接口信息的方式;
注解只在def语句中有效,在lambda表达式中无效;
匿名函数:lambda
生成函数对象的表达式形式:lambda arg1, arg2,... : expression
;
仅能在lambda主体中封装有限的逻辑进去,连if这样的语句都不能使用(if/else的三元表达式可以);
lambda中参数也可以设置默认参数,与def中一样;
lambda起到了一种函数速写的作用,允许在使用的代码内嵌入一个函数的定义;
lambda通常用来编写跳转表(jump table),也就是行为的列表或字典,按需执行相应的动作;
{
'got': (lambda: 2**4)
}
出于对可读性的要求,最好避免嵌套使用lambda;
map
函数会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含了所有函数调用结果的可迭代对象(可以再使用list收集所有结果);
# map的一般用法
list(map((lambda x:x + 3), [1,2,3]))
# map的高级用法:提供多个序列作为参数,它能够并行返回分别以每个序列中元素作为函数对应参数得到的结果
pow(3,4) # 81
list(map(pow,[1,2,3],[2,3,4])) # [1, 8, 81]
函数式编程工具:filter和reduce
map函数是函数式编程工具中最简单的内置函数代表;
函数式编程:对序列应用一些函数工具;
- filter 过滤出一些元素,返回可迭代对象
- reduce 对 每对元素 都应用函数 并得到运行结果
list(filter((lambda x:x > 0), range(-5, 5)))
reduce((lambda x,y : x + y), [1,2,3,4]) # 默认序列第一个元素为初始值
reduce((lambda x,y : x + y), 0, [1,2,3,4])
第20章 迭代器和解析,第二部分
列表解析 与 map:
list(map(ord, 'soam'))
[ord(x) for x in 'spam']
[x for x in range(5) if x%2 == 0]
list(filter((lambda x:x%2 == 0), range(5)))
[x ** 2 for x in range(10) if x%2 == 0]
list(map((lambda x:x**2), filter((lambda x:x%2 == 0), range(10))))
# 去掉读取line的结尾\n
[line.rstrip() for line in open('file').readlines()]
[line.rstrip() for line in open('file')]
list(map((lambda line: line.rstrip()), open('file')))
# 解析元组
[age for (name, age, job) in listoftuple]
list(map((lambda row:row[1], listoftuple)))
map和列表解析在解释器中以C语言速度运行的,比for循环快;
map和列表解析最大区别:
- map是一个迭代器,根据需求产生结果
- 为了同样地实现内存节省,列表解析必须编码为生成器表达式;
在需要是产生结果,有两种语言结构尽可能地延迟结果创建:
- 生成器函数:使用
yield
语句一次返回一个结果,在每个结果之间挂起和继续; - 生成器表达式:类似列表解析,按需产生一个对象,而非结果列表;
二者的最终实现都是通过迭代协议来执行延迟结果;
生成器函数
生成器函数
:送回一个值 并随后从其退出的地方继续的函数;随着时间产生一个值的序列;
- 使用def语句编写
- 创建时 自动实现迭代协议
- 自动在生成值的时刻挂起 并继续函数的执行;挂起时保存的状态包含整个本地作用域;
- 生成器yield一个值,而不是返回一个值;yield语句挂起该函数并向调用者发送一个值;但也保留了足够的状态使得函数能够从它离开的地方继续;
- 继续时,函数在上一个yield返回后立即继续执行;
迭代协议整合
可迭代对象定义了一个__next__
方法,返回迭代中的下一项,或者引发一个特殊的StopIteration
异常来终止迭代;
一个对象的迭代器用iter内置函数接收;
要支持该协议:
- 函数包含一条yield语句,该语句特别编译为生成器;调用时返回一个迭代器对象;
- 该对象支持用一个名为
__next__
的自动创建的方法来继续执行的接口; - 生成器函数中使用return语句,总是在def语句末尾,直接终止值的生成;
最终效果:生成器函数,编写包含yield的def语句,自动支持迭代协议;
def gensquares(N):
for i in range(N):
yield i ** 2
# 遍历生成器对象
for i in gensquares(5):
print(i) # 0 1 4 9 16
如果一个不支持迭代协议的对象进行这样的迭代,for循环会使用索引协议进行迭代;
生成器在内存使用和性能方面往往更好,允许函数避免临时再做所有的工作,如果列表很大或者在处理每一个结果都需要很长事件时,这一点尤其有用;
扩展生成器协议:send
- 值可以通过调用G.send(value)发送给一个生成器G;之后恢复生成器的代码;
def gen():
for i in range(10):
X = yield i
print(X)
G = gen()
next(G) # 0
G.send(77) # 77 1
G.send(88) # 88 2
next(G) # None 3
生成器表达式
生成器表达式
对比列表解析:
- 语法上和列表解析一样,但是是括在圆括号中而不是方括号中的;
- 不是在内存中构建结果,而是返回一个生成器对象;
# 列表解析
[x ** 2 for x in range(4)]
# 生成器解析
(x ** 2 for x in range(4))
# list内置调用一次生成所有结果
list(x ** 2 for x in range(4))
G = (x ** 2 for x in range(4))
next(G) # 0
next(G) # 1
next(G) # 4
next(G) # 9
next(G) # StopIteration
# 一般不会机械地使用next迭代器来操作生成器表达式,因为for循环会自动触发(其他迭代语境也会这样,如sum map sorted any all list内置函数等);
for num in (x ** 2 for x in range(4)):
print(num)
# 生成器表达式在其他括号内时 自身的括号非必须
sorted(x ** 2 for x in range(4))
sorted((x ** 2 for x in range(4)), recerse=True)
生成器表达式大体上可以认为是对内存空间的优化;对非常大的结果集是优选;
无论生成器函数,还是生成器表达式,返回都是一个可迭代对象——一个生成器;值得注意的是,生成器是单次迭代器;
生成器是单迭代对象:
- 只支持一次活跃迭代;
- 一个生成器的迭代器是生成器自身,在一个生成器上调用iter没有任何效果;
- 生成器运行到完成,迭代器用尽之后,需要产生一个新的生成器再次开始;
G = (x ** 2 for x in range(4))
iter(G) is G # True
# 自己实现一个zip
def myzip(*args):
iters = list(map(iter, args)) # map放入list中 是因为map返回的是一个单次可迭代对象,这里我们需要使用list内置函数来创建一个支持多次迭代的对象(一个列表)!!!?
while iters:
res = [next(i) for i in iters]
yield typle(res)
字典拥有在每次迭代中产生键的迭代器:x = iter(D)
基于类的迭代器:
- 定义一个特别的
__iter__
发迸发,由内置的iter函数调用,返回一个对象; - 该对象有一个
__next__
方法,由next内置函数调用; - 一个
__getitem__
索引方法作为迭代的退而求其次的选项也是可以的;
已经掌握的解析语法
- 列表解析
[x * x for x in range(10)]
- 集合解析
{x * x for x in range(10)}
- 字典解析
{x: x * x for x in range(10)}
- 生成器解析
(x * x for x in range(10))
集合解析和字典解析 只是把生成器表达式传递给类型名的语法糖:
set(x * x for x in range(10))
dict(x * x for x in range(10))
扩展的解析语法:
- 支持if子句过滤
- 支持多个for循环嵌套,
[x+y for x in [1,2,3] for y in [4,5,6]] # [5,6,7,6,7,8,7,8,9]
对模块计时
import time
import sys
if sys.platform[:3] == 'win':
timefunc = time.clock
else:
timefunc = time.time
start = timefunc()
...
elapsed = timefunc() - start
import time, sys
timefunc = time.clock if sys.platform == 'win' else time.time
def timer(func, *pargs, _reps=1000, **kargs):
start = timefunc()
for i in range(_reps):
ret = func(*pargs, **kargs)
elapsed = timefunc() - start
return (elapsed, ret)
def best(func, *pargs, _reps=50, **kargs):
best = 2 ** 32
for i in range(_reps):
(time, ret) = timer(func, *pargs, _reps=1, **kargs)
if time < best:
best = time
return (best, ret)
def power(X, Y): return X**Y
timer(power, 2, 32)
best(power, 2, 32)
使用Python标准库中的替代模块timeit
可以自动对代码计时,并且解决了一些特定平台问题;
函数陷阱注意事项
- 本地变量是静态检测的
- 默认参数是在def语句运行时评估并保存的,而非调用时;
- Python会将每一个默认参数保存为一个对象,并附在函数本身;
- 正式由于以上愿意,对可变的默认参数一定要很小心;
- 没有return语句的函数,自动返回None对象;
def saver(x=[]):
x.append(1)
saver() # [1]
saver() # [1, 1]
def saver(x=None):
if x is None:
x = []
x.append(1)
saver() # [1]
saver() # [1]
已经介绍过:在进行嵌套函数作用域查找时,处理嵌套的循环改变了的变量时要小心,所有的引用将会使用在最后的循环迭代中对应的值;作为替代,请使用默认参数来保持循环变量的值;