闭包和迭代器
一、函数名的运用:(函数名其实就是一个变量名)
当def定义后的函数名加上括号就可以调用函数了具体函数名有哪些运用哪?(注意函数的命名规则是和变量名的命名规则一样的)
1、函数名的内存地址:
def func(): print("呵呵") print(func) # 结果: <function func at 0x1101e4ea0> 就是一个变量
2、函数名可以赋值给其他变量(就像其他变量的赋值)
def func(): print("呵呵") print(func) a = func # 把函数当成⼀个变量赋值给另⼀个变量 a() # 函数调func()
3、函数名可以当做容器类元素
def func1(): print("呵呵") def func2(): print("呵呵") def func3(): print("呵呵") def func4(): print("呵呵") lst = [func1, func2, func3] #作为列表的元素储存,然后分别遍历 for i in lst: i()
4、函数名可以作为另一个函数的参数:
def func(): print("吃了么") def func2(fn): print("我是func2") fn() # 执行传递过来的fn print("我是func2") func2(func) # 把函数func当成参数传递给func2的参数fn.
5、函数名可以作为函数的返回值
def func_1(): print("这里是函数1") def func_2(): print("这⾥是函数2") print("这⾥是函数1") return func_2 fn = func_1() # 执行函数1. 函数1返回的是函数2, 这时fn指向的就是上⾯函数2 fn() # 执行上⾯返回的函数 def func(): def inner(): pass return inner func()() # 效果于上一个一样
考点:
def fun():
print('1')
def inner():
print('3')
return inner
print(fun()()) #运行结果1, 3, None 因为在调用内层函数时,没有返回值,所以千万别忘了None
ret = fun() #将return的函数地址赋值给ret,将内层的函数加载到全局作用域中
ret() #在全局作用域中调用内层函数
print(ret()) # 1, 3, None
inner() # 注意单独调用这一行是会报错的,因为是内层的函数的临时空间,全局不认识 NameError: name 'inner' is not defined
li = [] # [func,func,func] for i in range(3): def func(x): print(x*i) #i最后变成2 li.append(func) for func in li: # [func,func,func] func(2) # 4 4 4
二、闭包( 什么是闭包? 闭包就是内层函数 对外层函数(非全局)的变量的引⽤, 叫闭包 主要是和全局变量作区别 ) 换句话说就是当内层函数的要引用到外层函数的变量时,因为这一丝牵连会让外层函数的变量暂住内存
def func1(): name = "小珠" def func2(): print(name) # 闭包 凡是调用了不是全局变量的的过程,那么就称闭包 func2() func1() #结果: 小珠 考试题: name = '老男孩' def wraaper2(n): # n = '老男孩' 相当于 def inner(): print(n) inner() print(inner.__closure__) # (<cell at 0x0000025748836648: str object at 0x00000257487C9B10>,) wraaper2(name) # 这也是一个闭包,虽然name是一个全局变量但是waaper2中引用了n
1、我们可以使用__closure__来检测函数是否是闭包,使用函数名.__closure__返回cell就是闭包,返回None就不是闭包
def func1(): name = "小珠" def func2(): print(name) # 闭包 func2() print(func2.__closure__) # (<cell at 0x10c2e20a8: str object at 0x10c3fc650>,) 一般在和要检验的函数平行的位置进行检验 func1()
2、如何在函数外边调用多层内部函数哪?
def func1(): def func2(): def func3(): print("嘿嘿") return func3 return func2 func1()()() # 嘿嘿 对于多层嵌套,只需要一层层剥
3、 由它我们可以引出闭包的好处. 由于我们在外界可以访问内部函数. 那这个时候内部函数访问的时间和时机就不⼀定了, 因为在外部, 我可以选择在任意的时间去访问内部函数. 这 个时候. 想一想. 我们之前说过, 如果一个函数执行完毕. 则这个函数中的变量以及局部命名 空间中的内容都将会被销毁. 在闭包中. 如果变量被销毁了. 那内部函数将不能正常执⾏. 所 以. python规定. 如果你在内部函数中访问了外层函数中的变量. 那么这个变量将不会消亡. 将会常驻在内存中. 也就是说.使用闭包, 可以保证外层函数中的变量在内存中常驻. 这样做有什么好处呢? 非常⼤的好处. 我们来看一个关于爬⾍的代码
总结:1、保护你的变量不受外界影响
2、可以让变量常驻内层(因为不确定内层函数什么时候调用外层的变量,所以会常驻内存)
from urllib.request import urlopen def but(): content = urlopen("http://www.xiaohua100.cn/index.html").read() def get_content(): return content #感觉多此一步,实则是为了不让content不轻易消失 return get_content fn = but() # 这个时候就开始加载校花100的内容 # 后⾯需要用到这⾥面的内容就不需要在执行⾮常耗时的网络连接操作了 content = fn() # 获取内容 print(content) content2 = fn() # 重新获取内容 print(content2)
三、迭代器
1、我们之前一直在⽤用可迭代对象进⾏迭代操作. 那么到底什么是可迭代对象.
本小节主要讨 论可迭代对象. 首先我们先回顾⼀下⽬前我们所熟知的可迭代对象有哪些:
str, list, tuple, dict, set. 那为什么我们可以称他们为可迭代对象呢? 因为他们都遵循了可迭代协议.
什么是可迭代协议. ⾸先我们先看⼀段错误代码
# 对的 s = "abc" for c in s: print(c) # 错的 for i in 123: print(i)
# 结果: Traceback (most recent call last): File "/Users/sylar/PycharmProjects/oldboy/iterator.py", line 8, in <module> for i in 123: TypeError: 'int' object is not iterable 注意看报错信息中有这样一句句话. 'int' object is not iterable . 翻译过来就是整数类型对象是不可迭代的. iterable表⽰可迭代的. 表⽰可迭代协议.
那么如何进行验证你的数据类型是否符合可迭代协议. 我们可以通过dir函数来查看类中定义好的所有⽅法.
2、使用dir来查看该数据包含了那些方法用来遍历列表,字符串,元祖....可迭代对象
可迭代对象: Iterable, 里面有__iter__()可以获取迭代器, 没有__next__()
迭代器: Iterator, 里面有__iter__()可以获取迭代器, 还有__next__()
s = "我的哈哈哈" print(dir(s)) # 可以打印对象中的方法和函数 print(dir(str)) # 也可以打印类中声明的方法和函数 在打印结果中. 寻找__iter__ 如果能找到. 那么这个类的对象就是一个可迭代对象. ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mod__'……
我们发现list,tuple,dict,set都有__iter__功能,说明他们都是可迭代对象!
3、目前有两种方式可以检测一个对象是否是可迭代对象:
3.1、土方法:
lst = '我是你大帅哥' print('__iter__' in dir(lst)) #True print('__next__' in dir(lst)) #False it = lst.__iter__() #将lst转化为迭代器赋值给it print('__iter__' in dir(it)) #True 迭代器本来就是迭代对象 print('__iter__' in dir(it)) #True 迭代器拥有next方法可进行循环
3.2、高富帅方法:以通过isinstence()函数来查 看一个对象是什么类型的
l = [1,2,3] l_iter = l.__iter__() from collections import Iterable from collections import Iterator
print(isinstance(l,Iterable)) #True
print(isinstance(l,Iterator)) #False
print(isinstance(l_iter,Iterator)) #True
print(isinstance(l_iter,Iterable)) #True
4、(for循环的原理)综上. 我们可以确定. 如果对象中有__iter__函数. 那么我们认为这个对象遵守了可迭代协议. 就可以获取到相应的迭代器. 这里的__iter__是帮助我们获取到对象的迭代器.
我们使⽤迭代器中的__next__()来获取到一个迭代器中的元素. 那么我们之前讲的for的⼯作原理到底是什 么? 继续看代码
s = "我爱北京天安门" c = s.__iter__() # 获取迭代器 print(c.__next__()) # 使用迭代器器进行迭代. 获取⼀个元素 我 print(c.__next__()) # 爱 print(c.__next__()) # 北 print(c.__next__()) # 京 print(c.__next__()) # 天 print(c.__next__()) # 安 print(c.__next__()) # 门 print(c.__next__()) # StopIteration for循环的内部机制: for i in [1,2,3]: print(i) 使用while循环+迭代器来模拟for循环(必须要掌握) lst = [1,2,3] lst_iter = lst.__iter__() #获取迭代器 while True: try: #试试 i = lst_iter.__next__() #调用下一个元素 print(i) except StopIteration: #除了事我兜着 break
递归的最大层数是可以修改的
import sys
sys.setrecursionlimit(10) # 修改层数为10
1、猴子吃桃问题。猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。 第二天早上又将剩下的桃子吃掉一半,又多吃一个。以后每天早上都吃了前一天剩下的一半零一个。 到第N天早上想再吃时,见只剩下一个桃子了。求第一天共摘多少桃子。
这里我们需要列出迭代公式: f(n) = f(n-1)/2 - 1 表示第2天,是第一天的一半减1
def fun(n):
if n ==1:
return 1
return 2*fun(n-1)+2
print(fun(10)) #1534
总结
迭代器特点:
1. 节省内存.
2. 惰性机制
3. 不能反复, 只能向下执行.
:必须拥有__iter__方法和__next__方法。
我们可以把要迭代的内容当成子弹. 然后呢. 获取到迭代器__iter__(), 就把子弹都装在弹夹中. 然后发射就是__next__()把每⼀个子弹(元素)打出来. 也就是说, for循环的时候
. ⼀开始的时候是__iter__()来获取迭代器. 后⾯每次获取元素都是通过__next__()来完成的. 当程序遇到 StopIteration将结束循环.!
补充说明:
1、一般情况默认参数只能被计算一次。当默认值是一个可变对象(如列表、字典或大多数类的实例)时,情况就不一样了。例如,下面的函数在后续调用时累积传递给它的参数: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3)) # [1] [1, 2] [1, 2, 3] 2、注意在函数定义之前值,不会被之后的赋值影响。除非global一下。 i = 5 def f(arg=i): print(arg) i = 6 f() 结果# 5 3、[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] #[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)] 4、 vec = [[1,2,3], [4,5,6], [7,8,9]] [num for elem in vec for num in elem] # [1, 2, 3, 4, 5, 6, 7, 8, 9] 5、>>> matrix = [ ... [1, 2, 3, 4], ... [5, 6, 7, 8], ... [9, 10, 11, 12], ... ] >>> [[row[i] for row in matrix] for i in range(4)] #结果[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] 法二 >>> list(zip(*matrix)) [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
1.写函数,传入一个参数n,返回n的阶乘 例如:cal(7) 计算7*6*5*4*3*2*1 def mul(n): if n==1:return 1 else:return n*mul(n-1) print(mul(3)) 2、写函数,传入n个数,返回字典{‘max’:最大值,’min’:最小值} 例如:min_max(2,5,7,8,4) 返回:{‘max’:8,’min’:2}(此题用到max(),min()内置函数) def min_max(*args): dic ={} dic['max']=max(args) dic['min']=min(args) return dic print(min_max(1,2,3,4,5)) 3、99乘法表 def mul(): for i in range(1,10): for v in range(1,i+1): print(i,'*',v,'=',i*v,end=' ') print() mul()