名称空间
又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方
名称空间共3种,分别如下
- locals: 是函数内的名称空间,包括局部变量和形参
- globals: 全局变量
- builtins: 内置模块的名字空间
不同变量的作用域不同就是由这个变量所在的命名空间决定的。
作用域即范围
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效
查看作用域方法 globals(),locals()
作用域查找顺序
level = 'L0' n = 22 def func(): level = 'L1' n = 33 print(locals()) def outer(): n = 44 level = 'L2' print(locals(),n) def inner(): level = 'L3' print(locals(),n) #此外打印的n是多少? inner() outer() func()
问题:在inner()里的打印的n的值是多少?
LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
- locals 是函数内的名字空间,包括局部变量和形参
- enclosing 外部嵌套函数的名字空间
- globals 全局变量,函数定义所在模块的名字空间
- builtins 内置模块的名字空间
闭包
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
def outer(): name = 'alex' def inner(): print("在inner里打印外层函数的变量",name) return inner f = outer() f()
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
匿名函数
匿名函数就是不需要显式的指定函数名
#这段代码
def calc(x,y): return x**y print(calc(2,5))
#换成匿名函数
calc = lambda x,y:x**y print(calc(2,5))
你也许会说,用上这个东西没感觉有毛方便呀, 。。。。呵呵,如果是这么用,确实没毛线改进,不过匿名函数主要是和其它函数搭配使用的呢,如下
res = map(lambda x:x**2,[1,5,7,4,8]) for i in res: print(i)
输出
1 25 49 16 64
高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x,y,f): return f(x) + f(y) res = add(3,-6,abs) print(res)
只需满足以下任意一个条件,即是高阶函数
- 接受一个或多个函数作为输入
- return 返回另外一个函数
递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
def calc(n): print(n) if int(n/2) ==0: return n return calc(int(n/2)) calc(10)
输出
10 5 2 1
来看实现过程,我改了下代码
def calc(n): v = int(n/2) print(v) if v > 0: calc(v) print(n) calc(10)
输出
5
2
1
0
1
2
5
10
列表生成式
现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表里的每个值加1,
In [56]: a = [i+1 for i in range(10)] In [57]: a Out[57]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这样的写法就叫做列表生成式
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成(),
就创建了一个generator:
In [58]: L = [x * x for x in range(10)] In [59]: L Out[59]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] In [60]: g = (x * x for x in range(10)) In [61]: g Out[61]: <generator object <genexpr> at 0x7f0dc1fc5eb0>
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值:
In [62]: next(g) Out[62]: 0 In [63]: next(g) Out[63]: 1 In [64]: next(g) Out[64]: 4 In [65]: next(g) Out[65]: 9 In [66]: next(g) Out[66]: 16 In [67]: next(g) Out[67]: 25 In [68]: next(g) Out[68]: 36 In [69]: next(g) Out[69]: 49 In [70]: next(g) Out[70]: 64 In [71]: next(g) Out[71]: 81 In [72]: next(g) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-72-5f315c5de15b> in <module>() ----> 1 next(g) StopIteration:
generator保存的是算法,每次调用next(g)
就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
In [76]: g = (x * x for x in range(10)) In [77]: for n in g: ....: print(n) ....: 0 1 4 9 16 25 36 49 64 81
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
In [90]: from collections import Iterable In [91]: isinstance([], Iterable) Out[91]: True In [92]: isinstance({}, Iterable) Out[92]: True In [93]: isinstance('abc', Iterable) Out[93]: True In [94]: isinstance((x for x in range(10)), Iterable) Out[94]: True In [95]: isinstance(100, Iterable) Out[95]: False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了
*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
In [97]: isinstance((x for x in range(10)), Iterator) Out[97]: True In [98]: isinstance([], Iterator) Out[98]: False In [99]: isinstance({}, Iterator) Out[99]: False In [100]: isinstance('abc', Iterator) Out[100]: False
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
In [101]: isinstance(iter([]), Iterator) Out[101]: True In [102]: isinstance(iter('abc'), Iterator) Out[102]: True
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python3的for
循环本质上就是通过不断调用next()
函数实现的,例如:
for x in [1, 2, 3, 4, 5]: pass
实际上完全等价于:
# 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break