函数进阶
命名空间
namespace, 顾名思义, 就是存放名字的地方.举例:若声明变量 x = 1, 值1存放与内存中, 那变量名x 就存放在命名空间里. 命名空间是存放x 和 1 绑定关系的地方.
- 名称空间共3种,分别如下:
- locals: 当前所在的函数内的名称空间,包括局部变量和形参
- globals: 全局变量,函数定义所在模块最外层的名字空间
builtins: 内置模块的名字空间
- 作用域范围
- 全局范围:全局存活,全局有效
局部范围:临时存活,局部有效
- LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> builtins
- locals 是函数内的名字空间,包括局部变量和形参
- enclosing 外部嵌套函数的名字空间
- globals 全局变量,函数定义所在模块的名字空间
- builtins 内置模块的名字空间
闭包
初步理解: 函数定义和函数表达式位于另外一个函数体内(嵌套函数),外部函数返回的是内部函数的函数名,而且内部 函数可以直接访问他们所在的外部函数中声明的所有局部变量、参数, 当这样的一个内部函数在包含他们的外部函数之外被调用的时候,就会形成闭包。
定义: 返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,除了函数内部定义的局部变量,其次就是使用自己外层包裹的作用域,
def outer():
name = ‘jack‘
def inner():
print("在inner里打印外层函数的变量", name)
return inner
f = outer() # outer函数执行后等价于 f == inner
f() # inner()
装饰器
- 作用: 在不改变现有函数的代码的情况下,进一步增强函数的功能
- 前提条件: 能形成闭包, 函数作为参数传入上级函数的命名空间,供内部函数返回掉调用
使用装饰器版本
- 简单实例:
def f1(func):
def inner():
return func(n)
return inner
@f1
def f2(x):
print(x)
f2()
过程详细解析
- 给函数f2加上装饰器f2可以理解为f = f1(f2)这个过程,此时先执行等式右边内容,f2函数作为参数被传入外部函数的命名空间,存储起来;
- f1返回inner, 等价于f = inner;
- 这就形成了闭包,在inner函数外部函数f1的外部调用f()这个函数,就同时于调用了内部函数inner()
- 此时inner结果返回之前传入的参数函数(),即f2()
- 此时inner函数就会向外部函数的命名空间查询变量或参数,找到之前传入的f2函数参数
- 由于之前传入的函数名就是外部函数f2(),所以直接会调用外部函数f2()
- 最后,函数名修改后并不会影响函数内部代码执行,所以直接将f 改为 f2, 那么就是实现了装饰器功能, 在不改变f2代码的同时,在启动前会运行f1(),此时生成中间函数明变量,函数名如果是f2的话,那么在后续调用f2()的时候同时也就启动了函数f2()。
实例2:给代码加认证函数
USERNAME = ‘lynnfang‘
PASSWORD = ‘abc‘
lock_status = 0
login_status = 0
def login(func, *args):
def inner(*args, **kwargs):
global lock_status, login_status
if lock_status == 1:
print(‘您的账户已被锁定!‘)
else:
if login_status == 0:
n = 0
while n < 3:
username = input(‘>>> 用户名: ‘)
password = input(">>> 密码: ")
if username == USERNAME and password == PASSWORD:
n = 3
login_status = 1
return func()
else:
n += 1
else:
lock_status = 1
else:
return func()
return inner
@login
def china():
print(‘----中国专区----‘)
@login
def japan():
print(‘----日本专区----‘)
china()
japan()
代码解析
- 在不改变已写好china()和japan()函数代码的前提下,对其加用户认证,只有认证通过才能调用函数内部表达式
- 首先将执行代码脚本,python检测到 @ 符号,将会将对应的函数名称作为参数传入login()函数的命名空间
- f1 = inner, f2 = inner
- 由于无形的中间函数可以赋值为之前传入的函数名,所以当调用这个函数名时候等同于外部调用inner
- 进入inner函数后,声明全局变量后,就可以在嵌套函数内部修改全局变量值,同时,在嵌套函数内部也会将全局变量值作为逻辑语句的判断条件,但最后肯定会返回之前传入的函数调用()
- 进入login()函数的命名空间查找对应的函数参数,由于函数参数就是之前传入的外部函数,所以也就调用了外部函数,顺利进入外部执行函数内部
- 只要不退出程序,下一次进入login函数就会参考之前的全局变量来判断执行了,所以只要一次登录,以后就不需要重复登录了
生成器(generator)
特点: 生成器不会把结果保存在一个系列中,而是保持生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束。
生成器表达式: 通列表解析语法,只不过把列表解析的[ ]换成( )
与列表解析的差异:生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。
典型生成器
g = (i for i in range(1000)
,g就是生成器,next(g)就可以迭代出内部的值,直到清空整个生成器range()在底层就是用生成器实现的, 在Python2里,range(10)生产的就是列表,所以要用xrange(10),比较节约空间,python3优化了range(),结果是类生成器,
range(121212121212212)‘在python shell下可以马上生成,但是
list(range(121212121212121212))`可能会很慢甚至直接出现内存错误,因为列表需要将所有的值都读进内存,但是内存分配给列表的标准内存是有限的空间,所以会出现内存错误,此时生成器的有点就显现出来了生成器是一次性的,迭代完成会出现
StopIteration
错误
yield理解
- yield的作用类似与return;
- 当python识别到next()函数,就会开始进入函数内部执行,直到遇到yield关键字,然后向外部返回yield对应的值;
- 之后的每次调用都会从yield关键字开始向后执行代码,随后从函数头向下执行直到遇到yield返回值后就停止等待,直到遇到下一个next();
- g.send(’a‘)可以将’a‘赋值给yield
- 生成器实现range( )函数
def range2(end):
count = 0
while count < end:
n = yield count
count += 1
g = range2(10)
print(next(g))
print(next(g))
print(next(g))
g.send(‘stop‘)
- 将函数变成生成器,制作fabnacci序列生成器
def fab(max_num):
n, a, b = 0, 0, 1
while n < max_num:
yield b # 把函数执行过程冻结在这一步,并把b的值返回给外面的next(函数)
a, b = b, a+b
n += 1
g = fab(20)
print(g)
迭代器
可迭代对象(iterable):凡是可以用for循环的对象都是可迭代对象, 常见的数据集合list, tuple, set, dict, str都是可迭代对象
- 迭代器(iterator):
- 可以被next()调用并不断返回下一个值的对象就是迭代器;
- 表示的是一个数据流,一个有序序列
- 惰性,只有在需要返回下一个数据时才会计算
无法预知大小,没有len()这个方法
可迭代对象可以转换成迭代器, 直接调用iter()函数
判断可迭代对象:
isinstance(11, Iterable)
判断是否是迭代器:
isinstance((i for i in range(10)), Iterator)