函数进阶二(函数名运用,闭包,迭代器)
一. 函数内容回顾
万能参数:
*args **kwargs
*魔性用法
函数的定义时:* ** 代表聚合
函数的调用时:* ** 代表打散
形参顺序:
位置参数, *args, 默认参数, **kwargs
名称空间
临时名称空间
def func():
name = "alex"
func()
print(name) # 找不到
func() # 再次调用又可以
内置名称空间
作用域:
全局作用域:内置名称空间,全局名称空间
局部作用域:临时名称空间,也称局部名称空间
加载顺序:
内置名称空间 > 全局名称空间 > 临时名称空间
取值顺序:
就近原则, LEGB
临时名称空间 > 全局名称空间 > 临时名称空间
内置函数:
globals(): 返回一个字典, 全局作用域的内容。
locals(): 返回一个字典, 当前作用域的内容。
关键字:
global:
1. 在局部作用域可以声明一个全局变量
2. 在局部作用域可以对全局变量进行修改
nonlocal:
1. 不能对全局变量进行修改
2. 子级对父级函数的变量的修改
二. 函数名的运用
1 # 1. 函数名可以当作一个特殊的变量 2 3 def func(): 4 print(666) 5 print(func) 6 <function func at 0x00000203DE321E18> 7 8 # 这里是函数的内存地址 9 # 因此,函数名是一个特殊的变量
1 # 2. 函数名可以当做变量赋值 2 f1 = 2 3 f2 = f1 4 f3 = f2 5 print(f3) # 2 6 7 def func(): 8 print(666) 9 func1 = func 10 f2 = func1 11 f3 = f2 12 print(f3) 13 f3() 14 # 这个示例跟上面那个普通的变量的赋值是一样的意思
1 # 3. 函数名可以当作容器类数据类型的元素 2 def func1(): 3 print('in func1') 4 5 def func2(): 6 print('in func2') 7 8 def func3(): 9 print('in func3') 10 11 # 一次执行三个函数的办法 12 # 一开始只能这样 13 # func1() 14 # func2() 15 # func3() 16 17 # 现在可以这样: 18 l1 = [func1, func2, func3] 19 for i in l1: 20 i() 21 22 dic = {1: func1, 2: func2, 3: func3}
1 # 4. 函数名可以当作函数的参数 2 def func(x): 3 print(x) 4 print("in func") 5 6 def func1(): 7 print("in func1") 8 9 func(func1) 10 # <function func1 at 0x000002000651AC80> 11 # in func 12 13 def func(x): 14 x() 15 print("in func") 16 def func1(): 17 print("in func1") 18 19 func(func1) 20 # in func1 21 # in func
# 5. 函数名可以当作函数的返回值 def func(x): print("in func") return x def func1(): print("in func1") ret = func(func1) # 把 func1 当成一个变量x传入到func(x)中,打印 in func ret() # func(func1)返回的是一个指向 in func1 的函数名 # in func # in func1 # func(func1)() # 这个跟上面的一样 # func(func1()) # 这个是先调用func1,然后再调用 func(),把 func1 当作一个变量 # 函数名可以当作容器类数据类型的元素,函数名可以当作函数的参数,函数名可以当作函数的返回值,这三点得知函数是第一类对象
三. 闭包
1. 内层函数对外层函数(非全局)变量的引用和改变
2. 闭包只存在于内层函数中
3. 函数都要逐层返回,最终返回给最外层函数
1 # 这不是闭包 2 name = "alex" 3 def func(): 4 def inner(): 5 print(name) 6 return inner 7 func() 8 9 # 这就是标准闭包 10 def func(): 11 name = "alex" 12 def inner(): 13 print(name) 14 return inner 15 func() 16 17 # 这是闭包 18 def func(n): 19 def inner(): 20 print(n) 21 return inner 22 n = "太白" 23 func(n)() 24 25 def func(n): 26 n = name 27 def inner(): 28 print(n) 29 return inner 30 name = "太白" 31 func(name)() 32 # 这里和上面的一个意思 33 34 # 判断是不是闭包的方法 35 def func(): 36 name = "alex"
36 n = 12 37 def inner(): 38 print(name, n) 39 return inner 40 f = func() 41 # 获取闭包引用的外层变量 42 print(f.__closure__[0].cell_contents) # 注意要想判断是不是闭包,必须判断内层函数名的 __closure__ 方法,而不是外层函数名,这里 f=func()即是 inner 这个内层函数名 43 print(f.__closure__[1].cell_contents)
44 # 这里发现,就算先去掉第二个 print 的内容,还是先打印出 12,再打印出 alex
44 print(f.__closure__) 其实原因在 __closure__ 这个方法里,比如再加个 n1 = 0.02, 再 print(f.__closure__)
44 # (<cell at 0x00000264283E6618: int object at 0x0000000056526C60>,
44 # <cell at 0x00000264283E6648: float object at 0x00000264283011C8>, <cell at 0x00000264283E6678: str object at 0x000002642A018030>)
44 # 这里发现它内部就已经有打印数据类型的顺序了 44 45 name = "alex" 46 def func(): 47 def inner(): 48 print(name) 49 return inner 50 f = func() 51 print(f.__closure__[0].cell_contents) # 报错,说明不是闭包
1 # 闭包的作用: 2 def func(step): 3 num = 1 4 num += step 5 print(num) 6 func(3) # 4 7 8 # 临时名称空间 9 j = 0 10 while j < 5: 11 func(3) 12 j += 1 13 # 4 # 这一步函数执行完后,函数内变量与值的临时空间消失 14 # 4 15 # 4 16 # 4 17 # 4 18 19 # 闭包:解释器在执行程序时,如果遇到函数,随着函数的结束而关闭临时名称空间 20 # 但是遇到闭包,有一个机制:闭包的空间不会随着函数的结束而关闭 21 def wrapper(step): 22 num = 1 23 def inner(): 24 # num += step 25 # 单是上面这步是内层函数对外层函数的引用和修改,会出错 26 nonlocal num 27 num += step 28 print(num) 29 return inner 30 f = wrapper(3) 31 j = 0 32 while j < 5: 33 f() # inner() 34 j += 1 35 36 # 4 37 # 7 38 # 10 39 # 13 40 # 16 41 # 因为闭包的机制,num这个变量在函数第一次执行结束后不会马上消失,会在内层中保存一段时间,因为在下一个循环时,它会保存之前num对应的值 42 43 def wrapper(step): 44 num = 1 # 这个外层函数也不会关闭,因为只要内层函数有闭包不会关闭 45 def inner(): 46 nonlocal num 47 num += step 48 print(num) 49 return inner # 返回函数的变量名 50 # return inner() 一般不这样写 51 f = wrapper(3) 52 j = 0 53 while j < 5: 54 f() # inner() 55 # 如果这里改成wrapper(3)() 56 # 那就相当于在内层中开辟了5个空间,每个空间调用 wrapper(3) 57 # 因此打印结果是 4 4 4 4 4 58 j += 1
1 # 闭包的应用 2 # 1. 装饰器(它的本质是利用了闭包的原理) 3 # 2. 爬虫 4 5 from urllib.request import urlopen 6 7 content = urlopen("http://www.cnblogs.com/jin-xin/articles/8259929.html") 8 print(id(content)) # 2630731144832 9 # print(content.decode("utf-8")) 10 content = urlopen("http://www.cnblogs.com/jin-xin/articles/8259929.html") 11 print(id(content)) # 2630731219576 12 # 如果不用闭包,爬一次就得创建一个空间,创建多次就得在内层中创建多个空间 13 # 因此要使用闭包形式,爬一次在内层中创建一个空间后,再爬一次还是在同一个内层地址 14 # 如下所示 15 16 from urllib.request import urlopen 17 18 def index(): 19 url = "http://www.cnblogs.com/jin-xin/articles/8259929.html" 20 def get(): 21 return urlopen(url).read() 22 return get 23 24 taibai = index() 25 content = taibai() 26 # content = index()() 和上面两步一样 27 print(id(content)) # 2550328055264 28 print(id(content)) # 2550328055264 29 print(content.decode("utf-8")) 30 31 # 闭包就是在内层中开一个空间,常贮存一些内容,以便后续程序调用
1 # 迭代器 2 3 # iterable: 可迭代对象——只要内部含有__iter__方法的就是可迭代对象, 它遵循可迭代协议 4 # str list tuple dict range() 文件句柄 5 6 s1 = "abc" 7 print(dic(s1)) # 打印结果中含有__iter__方法 8 9 for i in "abc": 10 print(i) # 可执行 11 for i in 123: 12 print(i) # 报错 13 14 # 判断一个对象是否是可迭代对象的方法 15 # 第一种 16 s1 = "abc" 17 print("__iter__" in dir(s1)) # True 18 print("__iter__" in dir(range(10))) # True 19 20 # 第二种 21 from collections import Iterable 22 from collections import Iterator 23 24 s1 = "abcd" 25 obj = iter(s1) 26 print(obj) 27 28 print(isinstance(obj, Iterator)) # True 29 print(isinstance(obj, Iterable)) # True 30 31 print(isinstance(s1.Iterator)) # False 32 print(isinstance(s1.Iterable)) # False
1 # 可迭代对象不能直接取值,除了索引和切片 2 # 比如for...in...是将可迭代对象先转换成迭代器,再从迭代器里面通过next()方法取值的 3 # 因此迭代器的含义:内部含有"__iter__"并且含有"__next__"方法的就是迭代器,遵循迭代器协议 4 5 # 可迭代对象转换成迭代器的两种方法 6 # 可迭代对象.__iter__() 或者iter(可迭代对象) 7 s1 = "abcd" 8 obj = s1.__iter__() 9 print(obj) # <str_iterator object at 0x000001828C618400> 10 print(obj.__next__()) # a 11 print(obj.__next__()) # b 12 print(obj.__next__()) # c 13 print(obj.__next__()) # d 14 print(obj.__next__()) # 报错——StopIteration,因为一个next对应一个值,多一个就会报错 15 16 # 可迭代对象转换成迭代器 17 # 可迭代对象.__iter__() 或者iter(可迭代对象) 18 s1 = "abcd" 19 obj = iter(s1) 20 print(obj) # <str_iterator object at 0x0000010F49D58048> 21 print("__iter__" in dir(obj) and "__next__" in dir(obj)) # True 22 23 24 # 判断文件句柄是否是可迭代对象,是否是迭代器 25 from collections import Iterable 26 from collections import Iterator 27 28 f = open("s", encoding="utf-8", mode="w") 29 print(isinstance(f,Iterator)) 30 31 # s2 = [1, 2, 3] 32 # 将s2转换成迭代器进行取值 33 34 # 第一种 35 s2 = [1, 2, 3] 36 obj = s2.__iter__() 37 obj2 = iter(s2) 38 # print(obj2.__next__()) 39 print(next(obj2)) 40 41 # 第二种 42 s2 = [1, 2, 3] 43 obj = iter(s2) 44 # print(obj2.__next__()) 45 print(next(obj2))
1 # isinstance() 和 type() 的区别 2 3 s1 = "abcd" 4 print(isinstance(s1, str)) 5 print(type(s1)) 6 7 # True 8 # <class 'str'> 9 10 # type() 只是判断该对象的数据类型 11 # 而 isinstance() 不仅可以判断该对象的数据类型,还可以判断它的其他类型,比如它是否是迭代器之类的
1 # 迭代器的作用: 2 3 # 1. 节省内存 4 # 比如有1000个元素的列表,在内存中开辟1000个内存地址,而迭代器中的元素不管有多少,在内存中只占一个位置 5 6 # 2. 惰性机制 7 # next()就取一个值,不next()就不取值 8 9 # 3. 一条路走到黑,不走回头路 10 s2 = [1, 2, 3, 4, 5] 11 obj2 = iter(s2) # 迭代器 12 print(next(obj2)) # 前面的不能再取,只能运行完又从头开始运行 13 14 # 里面元素少的时候使用列表 15 # 元素很多的时候使用迭代器 16 # 比如上面提到的文件句柄,就是迭代器,文件里面有很多内容都可以很快打开读取
1 # while循环模拟for循环机制(很重要!) 2 l1 = [i for i in range(20)] 3 obj = iter(l1) 4 while 1: 5 print(next(obj)) # 会取出但是会报错,因为next()方法,不走回头路 6 7 # 因此可以使用异常处理 8 l1 = [i for i in range(20)] 9 obj = iter(l1) 10 while 1: 11 try: 12 print(next(obj)) 13 except StopIteration: 14 break 15 # 这样程序就能正常运行