第三章| 3.3 函数进阶
4、高阶函数
命名空间
又名name space, 顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方
名称空间共3种,分别如下
- locals: 是函数内的名称空间,包括局部变量和形参
- globals: 全局变量,函数定义所在模块的名字空间
- builtins: 内置模块的名字空间
不同变量的作用域不同就是由这个变量所在的命名空间决定的。
作用域即范围
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效
查看作用域方法 globals(),locals()
作用域查找顺序
LEGB:
L:locals ; E:enclosing相邻的 ; G:globls ; B:builtins
n = 10 def func(): n = 20 print('func:' ,n) #func:20 def func2(): n = 30 print(' func2' ,n) #func2:30 def func3(): print('func3:' , n) #func3:30 func3() func2() func()
LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
- locals 是函数内的名字空间,包括局部变量和形参
- enclosing 外部嵌套函数的名字空间
- globals 全局变量,函数定义所在模块的名字空间
- builtins 内置模块的名字空间
闭包
在函数里边又套了一层子函数,在子函数被返回了,就是当外层函数执行的时候子函数被返回了返回了内存地址;然后在外边执行这个子函数的时候它又引用了外边函数的这个变量,相当于这个子函数跟外层函数有某种扯不清的关系,这种关系就叫闭包。
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
装饰器( 闭包+函数的重新赋值)
软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块不应该被修改
- 开放:对现有功能的扩展开放
实现装饰器知识储备:
1. 函数即“变量”
2. 高阶函数; a、把一个函数名当做实参传入另一个函数 ;b、返回值中包含函数名(不修改函数的调用方式)
3. 嵌套函数
高阶函数+嵌套函数=》装饰器
在不改变原代码前提下给它加一个装饰的功能:搞一个高阶函数,把要修饰的当做参数传进去,然后在里边返回。
定义:本质是函数(装饰其他函数)就是为其他函数添加附加功能
原则:1.不能修改被装饰的函数的源代码;2.不能修改被装饰的函数的调用方式;
装饰器对它被装饰的函数是完全透明的,就是别人不知道你改了它的,没影响。
如:
编写装饰器,为每个函数加上统计运行时间的功能
提示:在函数开始执行时加上start=time.time()就可纪录当前执行的时间戳,函数执行结束后在time.time() - start就可以拿到执行所用时间
import time def deco(func): start_time = time.time() func() stop_time = time.time() print('the func run time is %s'%(stop_time - start_time)) def test1(): time.sleep(2) print('in the test1') deco(test1) #test1()加括号就是把它的运行结果传进去了; 这种调用方式改变了函数的调用方式。改变了test1的调用方式。test1=deco(test1) ---> test1();前提是把它修改成第二种形式的高阶函数加个return返回它的内存地址即它的返回值。 import time def timer(func): #timer(test1) func=test1 def deco(): start_time = time.time() func() #运行test1() stop_time = time.time() print('the func run time is %s'%(stop_time - start_time)) return deco #第二种形式的高阶函数 def test1(): time.sleep(2) print('in the test1') #print(timer(test1)) #返回的是deco的一个内存地址 test1 = timer(test1) #执行timer(test1)这个函数,把test1当做参数传进去;拿到一个返回结果deco; 代码走到这一步的时候会执行timer(test1),不会往下走了 test1() ## 实际上运行执行的是deco #既没有改变调用方式也没有改变原代码
#######初级装饰器 import time def timer(func): #timer(test1) func=test1 def deco(): start_time = time.time() func() #运行test1() stop_time = time.time() print('the func run time is %s'%(stop_time - start_time)) return deco @timer #--->> test1 = timer(test1) def test1(): time.sleep(2) print('in the test1') @timer #--->> test2 = timer(test2) def test2(): time.sleep(4) print('in the test2') test1() test2()
#####中级带参数装饰器 import time def timer(func): #timer(test1) func=test1 def deco(*args,**kwargs): start_time = time.time() func(*args,**kwargs) #运行test1() stop_time = time.time() print('the func run time is %s'%(stop_time - start_time)) return func #把函数的结果要给返回了 return deco @timer #--->> test1 = timer(test1) def test1(): time.sleep(2) print('in the test1') return 'from test1' @timer #--->> test2 = timer(test2) = deco test2(name,age)=deco(name,age) ==deco(arg1,arg2)-->func(arg1,arg2) def test2(name,age): time.sleep(4) print('test2',name,age) test1() test2('alex',22)
打印: in the test1 the func run time is 2.0001144409179688 test2 alex 22 the func run time is 4.0002288818359375
#####高级版 import time user,passwd = 'kris' , 'abc123' def auth(auth_type): #auth_type : 'local' print("auth func:" , auth_type) def outer_wrapper(func): def wrapper(*args, **kwargs): print("wrapper func args:" , *args,**kwargs) if auth_type == "local": username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[33;1m User has passed authentication \033[0m" ) res = func(*args,**kwargs) #from home print("-------after authentication") return res #这样就把home的执行结果返回了 else: exit("\033[31;lmInvalid username or password\033[0m") elif auth_type == "ldap": print("ladp呀呀") return wrapper return outer_wrapper def index(): print("welcome to index page") @auth (auth_type="local") #本地的认证 #home = wrapper();最终返回的是wrapper;你现在加上括号了,执行了; def home(): print("welcome to home page") return "from home" #home执行完之后返回的数据,不能改变执行结果;怎么获取home的返回结果呢 @auth(auth_type="ldap") #远程的认证 def bbs(): print("welcome to bbs page") index() print(home()) #相当于调用的是wrapper() bbs()
##打印: auth func: local auth func: ldap welcome to index page wrapper func args: Username:kris Password:abc123 User has passed authentication welcome to home page -------after authentication from home wrapper func args: ladp呀呀
#_*_coding:utf-8_*_ user_status = False #用户登录了就把这个改成True def login(func): #把要执行的模块从这里传进来 def inner(*args,**kwargs):#再定义一层函数 _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: func(*args,**kwargs) # 看这里看这里,只要验证通过了,就调用相应功能 return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 def home(): print("---首页----") @login def america(): #login() #执行前加上验证 print("----欧美专区----") def japan(): print("----日韩专区----") # @login def henan(style): ''' :param style: 喜欢看什么类型的,就传进来 :return: ''' #login() #执行前加上验证 print("----河南专区----") home() # america = login(america) #你在这里相当于把america这个函数替换了 henan = login(henan) # #那用户调用时依然写 america() henan("3p")
带参数的装饰器
#_*_coding:utf-8_*_ user_status = False #用户登录了就把这个改成True def login(auth_type): #把要执行的模块从这里传进来 def auth(func): def inner(*args,**kwargs):#再定义一层函数 if auth_type == "qq": _username = "alex" #假装这是DB里存的用户信息 _password = "abc!23" #假装这是DB里存的用户信息 global user_status if user_status == False: username = input("user:") password = input("pasword:") if username == _username and password == _password: print("welcome login....") user_status = True else: print("wrong username or password!") if user_status == True: return func(*args,**kwargs) # 看这里看这里,只要验证通过了,就调用相应功能 else: print("only support qq ") return inner #用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 return auth def home(): print("---首页----") @login('qq') def america(): #login() #执行前加上验证 print("----欧美专区----") def japan(): print("----日韩专区----") @login('weibo') def henan(style): ''' :param style: 喜欢看什么类型的,就传进来 :return: ''' #login() #执行前加上验证 print("----河南专区----") home() # america = login(america) #你在这里相当于把america这个函数替换了 #henan = login(henan) # #那用户调用时依然写 america() # henan("3p")
5、生成器&迭代器
列表生成器
直接对列表里边的值修改
>>>a = [ i*i for i in range(10)] >>>a [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>>a = list(range(10)) >>>a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,] >>>a = [ i if i < 5 else i*i for i in a] >>>a [0, 1, 2, 3, 4, 25, 36, 49, 64, 81 ]
生成器
range(0,10,2)代表指向了0,2,4,6,8这几个值,最后的2代表步长;
>>> m=range(10) >>> m[2] 2 >>> m[-1] 9 >>> m[5] 5
yield
语句可以让普通函数变成一个生成器,并且相应的 __next__()
方法返回的是 yield
后面的值。
一种更直观的解释是:程序执行到 yield
会返回值并暂停,再次调用 next()
时会从上次暂停的地方继续开始执行。
生成器的创建方式:1.列表生成式()(通过list函数生成列表); 2.函数
>>>a2 = (i for i in range(1000)) >>>a2 <generator object <genexpr> at 0x1014a73b8> ##生成了一个生成器 >>>next(a2) 0 >>>next(a2) 1 >>>next(a2) 2
上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10)) >>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81
def fib(max): n, a, b = 0, 0 ,1 while n < max: print('before yield') yield b #把函数的执行过程返回冻结在这一步了,并且把b的值返回给了外面的next() print(b) a, b = b, a+b n = n + 1 return 'done' f = fib(15) #turn function into a generator next(f) #first time call next() next(f) #first time call next() next(f) #first time call next() next(f) 打印出: before yield 1 before yield 1 before yield 2 before yield
斐波那契
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
主要就是yield后边那两句;里边有yield函数一加括号,代码根本不执行,只生成一个生成器。
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done’ >>> fib(10) 1 1 2 3 5 8 13 21 34 55 done
fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
##用函数写生成器 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>
用next调用它结果是一样的:
1
1
2
3
5
8
13
21
34
55
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
函数写生成器
def range2(n): count = 0 while count < n: #print(count) # count +=1 yield count #return print(range2(10)) #打印: <generator object range2 at 0x10159ac50> #一个生成器 打印: 1 2 3 4 5 6 7 8 9 10
def range2(n): count = 0 while count < n: print('count',count) count +=1 yield count #return new_range = range2(10) r1 = next(new_range) print(r1) for i in range2(10): print(i) 打印: count 0 1 count 0 1 count 1 2 count 2 3 count 3 4 count 4 5 count 5 6 count 6 7 count 7 8 count 8 9 count 9 10
def range2(n): count = 0 while count < n: print(‘count’ ,count) count +=1 yield count #return new_range = range2(10) r1 = next(new_range) print(r1) r2 = next(new_range) print('干别的事') print(r2) next(new_range) # == new_range.__next__() #这两个是一样的 new_range.__next__() 打印: count 0 1 count 1 干别的事 2 count 2 count 3
生成器调用方法
python2: range = list Python3: range = 生成器
xrange = 生成器 xrange | 没有
生成器send方法
只要函数里边有yield就是生成器了;
迭代器(循环)
范围比生成器大
可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
凡是可作用于for
循环的对象都是Iterable(可迭代)
类型;凡是可作用于next()
函数的对象都是Iterator(迭代器)
类型,它们表示一个惰性计算的序列;集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
可以使用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
Python3的for
循环本质上就是通过不断调用next()
函数实现的,例如:
for x in [1, 2, 3, 4, 5]: pass
实际上完全等价于:
# 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) next(it) 1 next(it) 2 # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break
创建一个迭代器有3种方法,其中前两种分别是:
- 为容器对象添加
__iter__()
和__next__()
方法(Python 2.7 中是next()
);__iter__()
返回迭代器对象本身self
,__next__()
则返回每次调用next()
或迭代时的元素; - 内置函数
iter()
将可迭代对象转化为迭代器; - 生成器,生成器通过
yield
语句快速生成迭代器,省略了复杂的__iter__()
&__next__()
方式。
创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即可。