1、函数嵌套
函数的嵌套定义:在一个函数的内部,又定义了另外一个函数
函数的嵌套调用:在调用一个函数的过程中,又调用了其他函数
代码:
>>> def f1(): ... def f2(): ... print(‘from f2‘) ... def f3(): ... print(‘from f3‘) ... f3() ... f2() ...
执行结果:
>>> f1() from f2 from f3
2、命名空间与作用域:存放名字的地方,确切的是存放的名字与变量值的绑定的关系的空间
名称空间分类:内置名称空间、全局名称空间和局部名称空间
内置名称空间:Python解释器启动时产生一些内置名字
全局名称空间:在执行文件时产生的,存放在文件级别(流程控制语句定义、未缩进定义的)定义的名字
局部名称函数:
加载顺序:内置---->全局---->局部
名称查找顺序:局部---->全局---->内置
作用域:作用的范围
作用域分类:全局作用域和局部作用域
全局作用域:全局存活,全局有效。查看函数globals(),显示字典类型
局部作用域:临时存活,局部生效。查看函数locals(),显示字典类型
关键字:global nonlocal
局部修改全局,对于不可变类型需要使用global,可变类型无需使用global
局部修改局部,对于不可变类型需要使用nonlocal,可变类型无需使用nonlocal
注意:尽量避免使用局部修改全局
重点:作用域关系,在函数定义时,函数中名称查找就已经固定和调用位置无关。在调用函数时,必须回到函数原来定义的位置去找作用域关系
x = 1 def f1(): def f2(): print(x) # x 为全局作用域 return f2() def foo(): x = 100 f1() foo() x = 10 foo()
3、闭包函数
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
闭包可以形象的把它理解为一个封闭的包裹,这个包裹就是一个函数,当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量,自由变量可以在随着包裹到处游荡。当然还得有个前提,这个包裹是被创建出来的。
在Python中可以这样理解,一个闭包就是我调用了一个函数A,这个函数A返回了一个函数B给我。这个返回的函数B就叫做闭包。我在调用函数A的时候传递的参数就是自由变量。
1. 定义
python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).这个定义是相对直白的,好理解的,不像其他定义那样学究味道十足(那些学究味道重的解释,在对一个名词的解释过程中又充满了一堆让人抓狂的其他陌生名词,不适合初学者)。下面举一个简单的例子来说明。
>>> def addx(x): ... def adder(y): return x + y ... return adder ... >>> c = addx(8) >>> type(c) <class ‘function‘> >>> c.__name__ ‘adder‘ >>> c(10) 18
结合这段简单的代码和定义来说明闭包:如果在一个内部函数里:adder(y)就是这个内部函数,对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,则这个内部函数adder就是一个闭包。
闭包=函数块+定义函数时的环境,adder就是函数块,x就是环境,当然这个环境可以有很多,不止一个简单的x。所以,如果要用一句话说明白闭包函数,那就是:函数内在包含子函数,并最终return子函数。
2. 使用闭包注意事项
2.1 闭包中是不能修改外部作用域的局部变量的
>>> def foo(): ... m = 0 ... def foo1(): ... m = 1 ... print(m) ... print(m) ... foo1() ... print(m) ... >>> foo() 0 1 0
从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。
2.2 以下这段代码是在python中使用闭包时一段经典的错误代码
>>> def foo(): ... a = 1 ... def bar(): ... a = a + 1 ... return a ... return bar ...
这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时
>>> c=foo() >>> print(c()) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bar UnboundLocalError: local variable ‘a‘ referenced before assignment
这是因为在执行代码c=foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单
def foo(): a = [1] def bar(): a[0] = a[0] + 1 return a[0] return bar
只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,所以在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。
4、装饰器:开发封闭原则:对扩展是开放的,对修改是封闭的。
5、迭代器
迭代:是一个重复的过程,每一次重复,都是基于上一次的结果而来
什么是迭代器对象
有__iter__和__next__方法,并且执行__iter__得到仍是迭代器本身
迭代器对象的优点
1. 提供了一种统一(不依赖索引)的迭代方式
2. 迭代器本身,比起其他数据类型更少内存,next()在内存中只有1个值
迭代器对象的缺点
1. 取值不灵活。只能往后走,不能回退,没有索引取值灵活
2. 无法预知取值结束,即无法预知长度
判断可迭代器对象与迭代器对象
from collections import Iterator
判断是否是可迭代器对象
print(isinstance(对象,Iterator))
判断是否是迭代器对象
6、生成器:在函数内存包含yield关键字,那么该函数执行的结果是生成器,并且生成器就是迭代器
关键字yield的功能
1. 包函数的结果做出迭代器,以一种优雅的方式封装好__iter__,__next__方法
2. 函数暂停与再继续运行的状态是有yield保存的
return 和 yield的区别:
功能相同:都能返回值
不同:return只能执行一次
7、三元表达式、列表解析、生成器表达式