第二模块:03python学习之函数进阶
Posted gbq-dog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第二模块:03python学习之函数进阶相关的知识,希望对你有一定的参考价值。
1.名称空间
定义:相比上一节的作用域,名称空间更能解释。名称空间又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方。
名称空间分以下三种:
- locals:是函数内的名称空间,包括局部变量和形参,当前函数空间内
- globals:全局变量,函数定义所在模块的空间
- bulildings:内置模块的名称空间
针对作用域的查找顺序,遵循LEGB的规则
- L:LOCALS 本地的
- E:ENCLOSING 相邻的
- G:GLOBLS 全局的
- B:BUILTINGS 内置函数的
函数在调用变量时,遵循从上到下,逐层向下获取变量,LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
看以下代码
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()
2.闭包
关于闭包可以这么定义:即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
是不是觉得很麻烦,绕来绕去,先看代码
def outer(): name = ‘alex‘ def inner(): print("在inner里打印外层函数的变量",name) return inner f = outer() f()
你会发现在与普通的嵌套函数不同的是子函数返回的是函数自己,而不是其他变量什么的。这个时候可以这么定义,在外部可以调用内部函数,同时使用内部的值这个就可以称之为闭包。虽然函数内部的子函数返回子函数自身,在返回的同时,不止是一个函数对象,同时在外面包裹了一层作用域,使得函数在调用的时候优先使用外部的作用域。
3.装饰器***
对于程序开发来说,我们一般需要遵守‘开放封闭原则’,所谓开放就是说程序有较好的拓展性以及延展性,因为对于一个程序的功能不可能永远不变,会各种各样的拓展功能,例如说我们登录一个程序,以前只有账户密码验证,后面铸件出现了QQ验证,微信认证,微博验证,这就要求我们的程序可以较好的拓展。而所谓的封闭就是说已经完成的程序代码最好不动,有可能的是这个操作会发生巨大的工作量,涉及到不同的部门或者不同的流程这样就会有巨大的变更动作,也有可能是代码时间已经很久远,不好重新构建。下面用几个例子来说明这个事情。
你所在的部门属于基础支撑部门,产生核心代码,其他部门代码从你这边调取
你的核心代码如下
############### 基础平台提供的功能如下 ############### def f1(): print ‘f1‘ def f2(): print ‘f2‘ def f3(): print ‘f3‘ def f4(): print ‘f4‘ ############### 业务部门A 调用基础平台提供的功能 ############### f1() f2() f3() f4() ############### 业务部门B 调用基础平台提供的功能 ############### f1() f2() f3() f4()
这个时候老板要求,核心代码随意调用有可能泄密,要求调用的时候需要账户密码验证。对于初学者我们有可能会想到这样,对每一个被调用的函数进行重编
代码如下
############### 基础平台提供的功能如下 ############### def f1(): # 验证1 # 验证2 # 验证3 print ‘f1‘ def f2(): # 验证1 # 验证2 # 验证3 print ‘f2‘ def f3(): # 验证1 # 验证2 # 验证3 print ‘f3‘ def f4(): # 验证1 # 验证2 # 验证3 print ‘f4‘ ############### 业务部门不变 ############### ### 业务部门A 调用基础平台提供的功能### f1() f2() f3() f4() ### 业务部门B 调用基础平台提供的功能 ### f1() f2() f3() f4()
但是这种情况,我们就发现重复会很多很多,这只是三四个模块,如果多呢?这样就太多重复代码了,有可能说我这样功能实现了呀,但是作为一个程序员应该是想着用最简洁的语句完成最多的事情,不过我们有可能在学了一点,想到以下的这种方式。
############### 基础平台提供的功能如下 ############### def check_login(): # 验证1 # 验证2 # 验证3 pass def f1(): check_login() print ‘f1‘ def f2(): check_login() print ‘f2‘ def f3(): check_login() print ‘f3‘ def f4(): check_login() print ‘f4‘
专门做一个认证函数,每个基础模块做修改调用一次认证函数,认证通过就可以继续下去,但是我们这样还是违背了开放封闭原则,对源代码做了修改,最好的办法就是认证一次后不需要在此认证。比较高级的代码就是下面这个,后面会对这个代码说明。
def w1(func): def inner(): # 验证1 # 验证2 # 验证3 return func() return inner @w1 def f1(): print ‘f1‘ @w1 def f2(): print ‘f2‘ @w1 def f3(): print ‘f3‘ @w1 def f4(): print ‘f4‘
解释上面的代码,对于python程序是由上至下执行,首先返回俩个值
return fi()直接执行f1函数
return inner返回inner函数,并没有执行
没错,从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。
从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章,@函数名 是python的一种语法糖。
如上例@w1内部会执行一下操作:
- 执行w1函数,并将 @w1 下面的 函数 作为w1函数的参数,即:@w1 等价于 w1(f1)
所以,内部就会去执行:
def inner:
#验证
return f1() # func是参数,此时 func 等于 f1
return inner # 返回的 inner,inner代表的是函数,非执行函数
其实就是将原来的 f1 函数塞进另外一个函数中 - 将执行完的 w1 函数返回值赋值给@w1下面的函数的函数名
w1函数的返回值是:
def inner:
#验证
return 原来f1() # 此处的 f1 表示原来的f1函数
然后,将此返回值再重新赋值给 f1,即:
新f1 = def inner:
#验证
return 原来f1()
所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在 新f1 函数内部先执行验证,再执行原来的f1函数,然后将 原来f1 函数的返回值 返回给了业务调用者。
如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着
所以我们可以这么理解,装饰器只是作为一个语法糖包含在函数外面,将函数作为参数传递给认证函数执行,对于调用者而言其实是执行,调用的f1(),其实是调用的inner函数,然后做认证,执行真正f1()函数
对于装饰器还有其他
带参数
#单参数 def w1(func): def inner(arg): # 验证1 # 验证2 # 验证3 return func(arg) return inner @w1 def f1(arg): print ‘f1‘ 一个参数 #俩个参数 def w1(func): def inner(arg1,arg2): # 验证1 # 验证2 # 验证3 return func(arg1,arg2) return inner @w1 def f1(arg1,arg2): print ‘f1‘ 两个参数 #多个参数 def w1(func): def inner(*args,**kwargs): # 验证1 # 验证2 # 验证3 return func(*args,**kwargs) return inner @w1 def f1(arg1,arg2,arg3): print ‘f1‘
双装饰器
def w1(func): def inner(*args,**kwargs): # 验证1 # 验证2 # 验证3 return func(*args,**kwargs) return inner def w2(func): def inner(*args,**kwargs): # 验证1 # 验证2 # 验证3 return func(*args,**kwargs) return inner @w1 @w2 def f1(arg1,arg2,arg3): print ‘f1‘
其他类型的需要后面再添加
4.列表生成器
现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式
第一种
a = [1,3,4,6,7,7,8,9,11] b = [] for i in a:b.append(i+1) a = b
第二种
b = [] >>> for i in a:b.append(i+1) >>> a = b
稍微装逼一点的
a =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] a = map(lambda x:x+1, a) for i in a:print(i)
最装逼的就是下面这种了
a = [i+1 for i in range(10)]
直接一行代码搞定,减少代码量,这种就叫做列表生成式
5.生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]
改成(),
就创建了一个generator:
第二种方法,yield 把函数变成生成器
5.1看以下代码
list1 = [i for i in range(100)] list1 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 , 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
我们很容易就用列表生成式生成一个100元素的列表,那么如果要生成一个一个出现的额生成式,只需要将[]变为()就可以了,看以下代码
>>> li = (i for i in range(100)) >>> li <generator object <genexpr> at 0x0220C6E8>
对于一个生成式获取他下一个元素就有俩种方式next(li) 和 li.__next__(),就可以轻松获取到下一个元素
不过需要注意的是,在调用next()的函数的时候,在最后一个位置会抛出一个错误,必须记住生成器只能往前,不能后退,也不可以切片,走到最后直接报错
>>> next(li) 8 >>> next(li) 9 >>> next(li) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration #每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
range对于python2与3的区别
在python3中,range和文件的打开也是生成器
5.2用yield语法生成一个生成器
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator
def fib(max): n,a,b = 0,0,1 while n < max: #print(b) yield b a,b = b,a+b n += 1 return ‘done‘ >>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>
yield与return的区别
return返回并中止function
yield返回数据,并冻结当前进程
next 唤醒冻结的函数执行过程,继续执行直到下一个yield
return在生成器中,代表生成器的中止,会直接报错
5.3生成器send
例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)
def fib(max): a, b, n = 0, 1, 0 while n < max: send_msg = yield b print(send_msg) a, b = b, a+b n += 1 f = fib(10) print(next(f)) f.send(‘寄个快递‘) print(next(f))
f.send(None) #第一次相当于f.__next__()
总结:
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
6.迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
- 一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等; - 一类是
generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance(‘abc‘, Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance(‘abc‘, Iterator) False
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter(‘abc‘), Iterator) True
总结
凡是可作用于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
以上是关于第二模块:03python学习之函数进阶的主要内容,如果未能解决你的问题,请参考以下文章