第16章 函数基础
为何使用函数
- 最大化的代码重用和最小化代码冗余
- 流出分解
编写函数
- def 是可执行的代码,当python运行到def时,函数就被创建了。但是当函数被调用的时候,函数才会被执行。
- def 创建了一个对象并且赋值给某一个变量
- lambda创建一个对象但将其作为结果返回
- return 返回结果
- yield 向调用者发回一个结果对象,同时记住它离开的地方
- global声明了一个模块级的变量并被复制。
- nonlocal声明了将要赋值的一个封闭的函数变量。
- 函数是通过赋值(对象引用)传递的。
- 参数、返回值以及变量并不是声明。
17章 作用域
在默认情况下,一个函数的所有变量名都是与函数命令空间关联的。即
- 一个在def内定义的变量名能够被def内的代码使用。但是不能被在函数外部引用这样的变量名。
- def 之中的变量名与def之外的变量名并不冲突。即def之外的变量名和def 之内的变量名毫无关系。
- 注意,每一个函数调用都会创建一个新的本地作用域。
变量名解析:legb原则
对于一个def语句:
- 变量名引用分为三个作用域进行查找,首先是本地,之后是函数内,之后是全局,最后是内置。
- 默认情况下,变量名赋值会创建或者改变本地变量。
- 全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域。
LEGB原则:
- 本地作用域
- def 中本地作用域(闭包)
- 全局作用域
- 内置作用域
global语句
全局变量如果在函数内部被赋值的话,必须经过声明
尽量少用 global
导入进来的别的模块的全局变量,也是全局变量。
nonlocal语句
用于定义函数时,内层变量引用外层变量,不然修改时会报错,类似global。
# 不改变变量时,不报错
In [133]: def tester(start):
...: state=start
...: def nested(label):
...: print(label,state)
...: return nested
In [134]: F=tester(0)
In [135]: F
Out[135]: <function __main__.tester.<locals>.nested(label)>
In [136]: F('damao')
damao 0
In [137]: F('ermao')
ermao 0
# 一旦改变变量,不用nonlocal声明,报错
In [138]: def tester(start):
...: state=start
...: def nested(label):
...: print(label,state)
...: state+=1
...: return nested
In [139]: F=tester(0)
In [140]: F('damao')
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-140-776037024ad4> in <module>()
----> 1 F('damao')
<ipython-input-138-c3d251ed857c> in nested(label)
2 state=start
3 def nested(label):
----> 4 print(label,state)
5 state+=1
6 return nested
UnboundLocalError: local variable 'state' referenced before assignment
# 内层函数用之前,一定要先声明,哪怕第一次使用时候,并没有改变变量
In [142]: def tester(start):
...: state=start
...: def nested(label):
...: print(label,state)
...: nonlocal state
...: state+=1
...: return nested
...:
...:
File "<ipython-input-142-a0b8a3802677>", line 5
nonlocal state
^
SyntaxError: name 'state' is used prior to nonlocal declaration
# 注意 不同的工厂函数可以生产出不同的值
In [143]: def tester(start):
...: state=start
...: def nested(label):
...: nonlocal state
...: print(label,state)
...: state+=1
...: return nested
In [144]: F=tester(0)
In [145]: F('damao')
damao 0
In [146]: F('ermao')
ermao 1
In [147]: F('sanmao')
sanmao 2
In [148]: F('damao')
damao 3
In [149]: F('ermao')
ermao 4
In [150]: G=tester(100)
In [151]: G('damao')
damao 100
In [152]: G('damao')
damao 101
In [153]: F('damao')
damao 5
In [154]: F('damao')
damao 6
还有一个注意的是,nonlocal声明的变量,毕竟在上层def中已经存在,不然立马报错。
另外,nonlocal 只会在嵌套的def中去查找变量,不会上升到全局。
# 如果不使用nonlocal声明,可以绑定内层函数
In [168]: def tester(start):
...: def nested(label):
...: print(label,nested.state)
...: nested.state+=1
...: nested.state=start
...: return nested
In [169]: F=tester(1)
In [170]: F('damao')
damao 1
In [171]: F('damao')
damao 2
In [172]: F('damao')
damao 3
In [173]: G=tester(1000)
In [174]: G('damao')
damao 1000
In [175]: G('damao')
damao 1001
列举python中保持状态的方法?
- 共享的全局变量
- 嵌套函数中嵌套函数的作用域引用
- 使用默认参数值来让一个python函数保持状态信息
- 利用函数属性将状态加载到函数自身(上个例子)
- 使用类
第18章 参数
- 参数的传递是通过自动将对象赋值给本地变量名来实现的。指针实现。
- 在函数内部,参数名的赋值不会影响调用者。
- 改变函数的可变对象参数的值也许会对调用者有影响。
通过对象引用传递:
- 不可变参数通过值进行传递。效果上就是一份拷贝。
- 可变对象通过指针进行传递。
python类就是依靠传入的self来更新对象状态。
特定参数匹配模型
python内部赋值进行参数匹配:
- 通过位置分配非关键字参数
- 通过匹配变量名分配关键字参数
- 其他额外的非关键字参数分配到*name元组中
- 其他额外的关键字参数分配到** name字典中
- 用默认值分配给在头部位得到分配的参数
模拟 python print函数
import sys
def print_redef(*args, **kargs):
sep = kargs.get('sep', ' ')
end = kargs.get('end', '\n')
file = kargs.get('file', sys.stdout)
output = ''
for arg in args:
output += str(arg) + str(sep)
file.write(output[:-len(sep)] + end)
def print_redef_1(*args, **kargs):
sep=kargs.pop('sep', ' ')
end=kargs.pop('end', '\n')
file=kargs.pop('file',sys.stdout)
if kargs:raise TypeError('extra keywords:' % kargs)
output = ''
for arg in args:
output += str(arg) + str(sep)
file.write(output[:-len(sep)] + end)
def print_redef_2(*args, sep=' ', end='\n', file=sys.stdout):
output = ''
for arg in args:
output += str(arg) + str(sep)
file.write(output[:-len(sep)] + end)
print_redef(1, 2, 3)
print_redef(1, 2, 3, sep='--')
print_redef(1, [2, 20], (3, 30), sep='--')
print_redef_2(1, 2, 3)
print_redef_2(1, 2, 3, sep='__', end='!\n')
print_redef_2(1, 2, 3, sep='__')
第19章 函数的高级话题
函数设计概念
- 耦合性:对于输入使用参数并且对于输出使用return语句
- 耦合性:只有在真正必要的情况下使用全局变量
- 耦合性:不要改变可变类型的参数,除非调用者希望这么做。
- 聚合性:每一个函数都应该有一个单一的,统一的目标。
- 大小:每一个函数应该相对较小
- 耦合:避免直接改变在另一个模块中的变量。
递归
即函数内调用本身
In [176]: def mysum(L):
...: print(L)
...: if not L:
...: return 0
...: else:
...: return L[0]+mysum(L[1:])
...:
In [177]:
In [177]: mysum([1,2,3,4,5,6])
[1, 2, 3, 4, 5, 6]
[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]
# 计算一个嵌套的子列表结构中所有数字和
In [184]: def sumtree(L):
...: tot=0
...: for x in L:
...: if not isinstance(x,list):
...: tot+=x
...: else:
...: tot+=sumtree(x)
...: return tot
...:
...:
In [185]: sumtree([1,[2,[3,4,[5,6,[7,8],10]]]])
Out[185]: 46
函数对象:属性和注解
匿名函数
- lambda是一个表达式,不是一个语句
- lambda的主题是一个单个的表达式,而不是一个代码块
第20章 迭代和解析,第二部分
python对于延迟的只是:
- 生成器函数
- 生成器表达式
生成器函数
状态挂起
生成器yield一个值,而不是return一个值,yield语句挂起函数后,向调用者发送一个值,保留足够的状态能够从它离开的地方继续。
迭代协议整合
可迭代对象定义了一个<next>方法,它要么返回迭代中的下一项,要么引发一个特殊的StopIteration。一个对象的迭代器用iter内置函数接受。
生成器是单迭代器对象
# 实现map函数
def mymap(func, *args):
res = []
for arg in zip(*args):
res.append(func(*arg))
return res
def mymap(func, *args):
return [func(*arg) for arg in zip(*args)]
# yield版本,包含进入list中即可一次产生所有值
def mymap(func, *args):
for arg in zip(*args):
yield func(*arg)
def mymap(func, *args):
return (func(*arg) for arg in zip(*args))
# print(mymap(abs,[1,2,-1,-100,2]))tuple
# print(list(mymap(abs,[1,2,-1,-100,2])))
# 截断zip
def myzip(*seqs):
seqs = [list(S) for S in seqs]
print(seqs)
res = []
# 利用数组原处改动的原理
while all(seqs):
# 相当于列表解析
res.append(tuple(S.pop(0) for S in seqs))
return res
# 不截断zip
def myzip(*seqs, pad=None):
seqs = [list(S) for S in seqs]
res = []
while any(seqs):
# pop完list之后是一个空数组
res.append(tuple((S.pop(0) if S else pad) for S in seqs))
return res
# 改成yield版本
def myzip(*seqs):
seqs = [list(S) for S in seqs]
while all(seqs):
yield tuple(S.pop(0) for S in seqs)
def myzip(*seqs, pad=None):
seqs = [list(S) for S in seqs]
while any(seqs):
# pop完list之后是一个空数组
yield tuple((S.pop(0) if S else pad) for S in seqs)
# print(list(myzip([1,2,3],[4,5,6,7,8],[100,299,222])))
# 采用了判断长度的方法,返回值换成圆括号,就是生成器
def myzip(*seqs):
minlen = min(len(S) for S in seqs)
return [tuple(S[i] for S in seqs) for i in range(minlen)]
def myzip(*seqs, pad=None):
maxlen = max(len(S) for S in seqs)
return [tuple((S[i] if len(S) > i else pad) for S in seqs) for i in range(maxlen)]
# 为什么这里while iters 可以知道长度。
def myzip(*args):
iters=list(map(iter,args))
while iters:
res=[next(i) for i in iters]
yield tuple(res)
print(list(myzip([1, 3, 4, 5], [6, 7, 8, 9, 10])))
列表解析的语法
In [238]: [x*x for x in range(5)]
Out[238]: [0, 1, 4, 9, 16]
In [239]: [x*x for x in range(5) if x%2 == 0]
Out[239]: [0, 4, 16]
In [240]: [x*x if x%2 ==0 for x in range(5)]
File "<ipython-input-240-7254a199a58d>", line 1
[x*x if x%2 ==0 for x in range(5)]
^
SyntaxError: invalid syntax
In [241]: [(x*x if x%2 ==0) for x in range(5)]
File "<ipython-input-241-100abab91a2e>", line 1
[(x*x if x%2 ==0) for x in range(5)]
^
SyntaxError: invalid syntax
In [242]: [(x*x if x%2 ==0 else 0) for x in range(5)]
Out[242]: [0, 0, 4, 0, 16]
In [243]: [(x*x if x%2 ==0 else None) for x in range(5)]
Out[243]: [0, None, 4, None, 16]
注意map函数可能比列表解析快,他俩都比for要快的多
函数的陷阱
- 本地变量是静态检测的。当编译def代码时,不是通过发现赋值语句在运行时进行检测的。python看到赋值语句,就是认为赋值语句就是本地变量。
- 默认和可变对象。这个参数看起来是全局变量,其实是本地变量,它不会与其他变量发生冲突。
In [245]: def saver(x=[]):
...: x.append(1)
...: print(x)
...:
In [246]: saver([2])
[2, 1]
In [247]: saver()
[1]
In [248]: saver()
[1, 1]
In [249]: saver()
[1, 1, 1]
In [250]: saver()
[1, 1, 1, 1
In [251]: def saver(x=None):
...: if x is None:
...: x=[]
...: x.append(1)
...: print(x)
...:
In [252]: saver([2])
[2, 1]
In [253]: saver([2,2])
[2, 2, 1]
In [254]: saver()
[1]
In [255]: saver()
[1]
In [256]: saver()
[1]
# 利用函数的属性记录状态
In [257]: def saver():
...: saver.x.append(1)
...: print(saver.x)
...:
In [258]: saver.x=[]
In [259]: saver()
[1]
In [260]: saver()
[1, 1]
In [261]: saver()
[1, 1, 1]
python迭代器和生成器的区别
- 容器(container):
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in
, not in
关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)在Python中,常见的容器对象有:
- list, deque, ....
- set, frozensets, ....
- dict, defaultdict, OrderedDict, Counter, ....
- tuple, namedtuple, …
- str
- 可迭代对象
但凡是可以返回一个迭代器的对象都可称之为可迭代对象,可迭代对象实现了__iter__
方法,该方法返回一个迭代器对象。
- 迭代器(iterator)
那么什么迭代器呢?它是一个带状态的对象,他能在你调用next()
方法的时候返回容器中的下一个值,任何实现了__iter__
和__next__()
(python2中实现next()
)方法的对象都是迭代器,__iter__
返回迭代器自身,__next__
返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。
所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。有很多关于迭代器的例子,比如itertools
函数返回的都是迭代器对象。
- 生成器(generator)
生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()
和__next__()
方法了,只需要一个yiled
关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。
- 生成器表达式(generator expression)
生成器表达式是列表推倒式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。