函数进阶二(函数名运用,闭包,迭代器)

时间:2021-10-15 22:44:20
一. 函数内容回顾
万能参数:
*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) 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():
 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 # 这样程序就能正常运行