python 之 函数进阶
命名空间
概念
:就是存放名字的地方,存什么名字呢?
举例说明:若x=1,1存放于内存中,那名字x存放在哪呢?
名称空间正是存放名字x 于 1 绑定关系的地方
命名空间分为三种
全局命名空间:Locals:是函数内的名称空间,包括局部变量和形参,打印当前所在的名称空间的所有变量
局部命名空间:Globals:全局变量,打印程序所有的变量。
内置命名空间:Builtins:内置模块的名字空间,dir(__builtins__)
不同变量的作用域不同就是由这个变量所在的命名空间决定的
作用域的作用范围
- 全局范围:全局存活,全局生效
- 局部范围:临时存活,局部有效
作用域查找顺序
LEGB
L:locals
E:enclosing 相邻的上一级
G:globls
B:buitlins ..
作用域链
函数名的本质
函数名的本质就是函数的内存地址
- 可以被引用
- 可以被当做容器类型的元素
- 可以当做函数的参数和返回值
闭包
概念:
这样理解:我们在函数里有子函数,子函数名被返回,在外部拿到了里边的函数,执行内部函数,利用内部作用域里所有的值,
或者这样:在外层函数被执行的时候,子函数名被返回了(返回的是内存地址),在外面执行内部函数的时候,他又引用了外层函数的变量。
格式如下,最为直接
def func(): name = 'eva' def inner(): print(name) return inner f = func() # 在此时,函数并没有执行 f()
装饰器
装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展
装饰器——简单版1
import time def func1(): print('in func1') def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner func1 = timer(func1) func1()
装饰器语法糖,这样就不用每次调用了,直接放在需要的函数“头部”
import time def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner @timer #==> func1 = timer(func1) def func1(): print('in func1') func1()
我们可以给装饰器传递参数,并且可以是多个参数
import time def timer(func): def inner(*args,**kwargs): start = time.time() re = func(*args,**kwargs) print(time.time() - start) return re return inner @timer #==> func1 = timer(func1) def func1(a,b): print('in func1') @timer #==> func2 = timer(func2) def func2(a): print('in func2 and get a:%s'%(a)) return 'fun2 over' func1('aaaaaa','bbbbbb') print(func2('aaaaaa'))
函数有返回值,一定要加上return,带返回值的装饰器
import time def timer(func): def inner(*args,**kwargs): start = time.time() re = func(*args,**kwargs) print(time.time() - start) return re return inner @timer #==> func2 = timer(func2) def func2(a): print('in func2 and get a:%s'%(a)) return 'fun2 over' func2('aaaaaa') print(func2('aaaaaa'))
刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效
def index(): '''这是一个主页信息''' print('from index') print(index.__doc__) #查看函数注释的方法 print(index.__name__) #查看函数名的方法
为了不让他们失效,我们还要在装饰器上加上一点来完善它:调用工具箱的wraps
from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper @deco def index(): '''哈哈哈哈''' print('from index') print(index.__doc__) print(index.__name__)
装饰器的固定格式
def timer(func): def inner(*args,**kwargs): '''执行函数之前要做的''' re = func(*args,**kwargs) '''执行函数之后要做的''' return re return inner
装饰器固定格式----带wraps
from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper
带参数的 装饰器,在不删除装饰器的情况下(因为可能有成千上万个啊)可以随时设置是否使用装饰器,加标志位
def outer(flag): def timer(func): def inner(*args,**kwargs): if flag: print('''执行函数之前要做的''') re = func(*args,**kwargs) if flag: print('''执行函数之后要做的''') return re return inner return timer @outer(False) def func(): print(111) func()
多个装饰器装饰同一个函数,
def wrapper1(func): def inner(): print('wrapper1 ,before func') func() print('wrapper1 ,after func') return inner def wrapper2(func): def inner(): print('wrapper2 ,before func') func() print('wrapper2 ,after func') return inner @wrapper2 @wrapper1 def f(): print('in f') f()
思考一下?两个装饰器的执行顺序
可迭代对象、迭代器,生成器
要了解生成器,我们先搞明白可迭代对象吧
迭代:和循环相似,基本可以画等号
迭代对象:可用于for循环的对象
有哪些可迭代对象:list,tuple,dict,set,str和generator
迭代器:可以被next()函数调用并返回下一个值得对象称为 迭代器:Iterator生成器:是一种生成的迭代器,显著特征是yield
列表生成式
我们之前已经了解了,并用过了这个工具
>>> a_list = [i for i in range(5)] >>> a_list [0, 1, 2, 3, 4] >>> type(a_list) <class 'list'>
生成器
只需要把[] 换成()。我们来看一下区别
>>> a_generator = (i for i in range(5)) >>> a_generator <generator object <genexpr> at 0x000001C7E8820200> >>> type(a_generator) <class 'generator'> >>> next(a_generator) 0 >>> next(a_generator) 1
我们看见,它并不会生产一个类似列表的东西 ,而是一个“generator object”,叫做生成器对象
通过查看类型,我们发现是一个“generator ”
通过使用next()函数,可以依次取到里面的值,直到取完,然后报错
>>> next(a_generator) 4 >>> next(a_generator) Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> next(a_generator) StopIteration
现在总结一下生成器的特点
1、不会主动产生,只有在需要的时候,可以通过next()获取,并且是随时取,随时停
2、随时取,随时停,有记忆功能,紧跟在上次取得值得后面
3、无法通过索引取值,不能切片,他已经不是一个列表或者元组对象了
4、只能往前走,也就是,取一个,生成器里可取的值就少一个,无法返回
5、取完了所有的值,就会报错
生成器的优点
节省内存空间,因为只在需要的时候生产
想想,我们刚才只是range(5),当然运行起来很流畅
但是,如果是range(100000000),用列表生成式的话,一不小心电脑就会卡死的
而生成器不会
你get到了吗?
顺便提一下在python2里面的的xrange和range
在python3中是这样的
>>> range(10) range(0, 10) >>> m = range(10) >>> type(m) <class 'range'>
在python2是这样的
>>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> xrange(10) xrange(10) >>> m = range(10) >>> type(m) <type 'list'>
仅此而已
迭代器的next()方法
可能你已经发现了,我们每次都要用next()函数去手动调用,这是很累的,如果有100000000个....
这种脏活应该交给电脑去做啊
用for循环
>>> for i in a_generator: print(i) 0 1 2 3 4 5 6 7 8 9
用while 循环
>>> while True: next(a_generator) 0 1 2 3 4 5 6 7 8 9 10 11
运行到最后,会发现(我真的等到最后了...
用while到结束后会报错,所以我们一般用for
你会发现挺管用的是不是,但是它好像一旦开始,就停----不----下----来
这时,该拿出神器了(如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:)
用法提示:
1、yield 必须放在函数中
2、如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
3、generator在每次调用next(i)或者__next()__的时候执行,遇到yield语句返回,再次被next()调用时从上次返回的yield语句处继续执行。
4、yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
def fib(max):
n, a, b = 0, 0, 1
while n < max: # 一定要有停止条件,要不就会无限循环下去
yield b # 使用yield 返回结果
a, b = b, a + b
n += 1
data = fib(100)
print(data)
print(next(data))
print(data.__next__())
print('去干点其他事情')
print(data.__next__())
print(next(data))
print('下面用for循环自动获取')
# 因为有记忆功能,所以会接着上次的继续
for i in data:
print(i)
还是上面的例子,先看效果
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n += 1
return 'Done'
data = fib(5)
print(data)
print(next(data))
print(next(data))
print(data.__next__())
print(data.__next__())
print('去干点其他事情')
print(data.__next__())
print(data.__next__())
print(next(data))
print(next(data))
print('下面用for循环自动获取')
如果你运行了这段代码,会发现报错,那就对了,而且错误是StopIteration:+函数的返回值
尝试用for循环
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a+b n += 1 return 'Done' data = fib(5) for i in data: print(i)
这样好像就不报错了,可是,我们的返回值呢,该怎么获取呢?
返回值包含在StopIteration的value中,关于如何捕获错误,后面的错误处理继续学习吧。以后一定补上
总结一下:
函数里有yield 之后,意味着函数func()就变成了生成器.生成器函数。
yield 和 return 的作用很相似(但是return表示终止),把结果返回到函数外边,交给调用者。
启动生成器有三种方法
1、next(i)或者i.__next__(),这是很原始的方法,一般不会用
2、while循环,其实也是基于next(),结束会报异常
3、for循环,拿不到返回值,结束后不报异常,要通过其他方法获取retrun的返回值
如果有retrun,代表终止,返回值会发给StopIteration
生成器的send方法
怎么才能让生成器随时立即停止呢?用send方法
def fib(max_num): n, a, b = 0, 0, 1 while n < max_num: sign = yield b if sign == 'stop': return 'KO' # 或者使用break,只是返回值是Done # break a, b = b, a + b n += 1 return 'Done' data_fib = fib(10) next(data_fib) # data_fib.send(None) # 或者使用开始第一个,send的参数一定要是None data_fib.send(None) # seed方法 发送消息给生成器内部 next(data_fib) fib_1 = next(data_fib) print(fib_1) data_fib.send('stop')
总结一下:
1、send:获取下一个值的效果和next基本一致,即,唤醒并继续执行;
2、可以发送一个信息到生成器内部,yield就是一个默认发送None的send消息;
3、只是在获取下一个值的时候,给上一yield的位置传递一个数据,我们可以利用这个获得的数据做点什么。
使用send的注意事项
1、第一次使用生成器的时候 是用next获取下一个值,也可以用send(None)而send(其他内容)不会有效;
2、最后一个yield不能接受外部的值
可迭代对象(iterable),迭代器(iterator),生成器(generator)的关系
定义:可以直接作用于for 循环的数据统称为 可迭代对象
一类是集合类型,一类是generator(包括生成器和带yield的generator function)
可以被next()函数调用并返回下一个值得对象称为 迭代器:Iterator
可以通过iter()把iterable 变成iterator
用isinstance()判断一个对象是否是iterable 对象
Isinstance(‘abc’,iterable)会返回布尔值以判断
为什么list dict 等不是Iterator
因为iterator是一个数据流,可以无限大,不知道它什么时候结束,可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能通过next()函数实现按需计算下一个数据,它是有惰性的,只有在需要返回时,才会计算
迭代器一定是迭代对象,
迭代对象不一定是迭代器,可以对迭代对象使用iter()方法来生成迭代器。
生成器一定是迭代器,
迭代器不一定是生成器,生成器更高级,有两种类型(生成器函数,和生成器表达式)