函数名的运用
函数名是一种特殊的变量,函数名加上括号后表示函数执行,除此之外,函数名还可以进行如下几条操作:
1. 作为变量赋值
1 def func():2 print(666)3 4 5 f1 = func6 f2 = f17 f2() # 打印666
2. 作为容器内数据类型的元素
应用场景:需要调用多个函数的时候
方法一:
1 def func1(): 2 print(111) 3 4 5 def func2(): 6 print(222) 7 8 9 def func3():10 print(333)11 12 13 func_list = [func1, func2, func3]14 for func in func_list:15 func()
执行结果
111222333
方法一里面是按照顺序执行函数,如果我们指定要执行某些函数呢,来看方法二
方法二:
1 def func1(): 2 print(111) 3 4 5 def func2(): 6 print(222) 7 8 9 def func3():10 print(333)11 12 13 dic = {14 1: func1,15 2: func2,16 3: func317 }18 19 for k in dic.keys():20 dic[k]()
把函数放在字典里,想执行哪个就执行哪个
3. 作为函数的参数
1 def func(f):2 f()3 4 5 def func1():6 print(222)7 8 9 func(func1) # 222
4. 作为函数的返回值
1 def func(x):2 return x3 4 5 def func1():6 print("in func1")7 8 9 func(func1)() # in func1
实际上,函数名是第一类对象,第一类对象的特点:
1. 可在运行期间创建
2. 可用作函数参数或返回值
3. 可存入变量的实体
笼统来说,第一类变量就是普通变量
闭包
首先抛出一个问题:为什么要有闭包?来看两个例子
实例一:
1 def func(step): 2 num = 1 3 num += step 4 print(num) 5 6 7 j = 0 8 while j < 5: 9 func(3)10 j += 1
执行结果
44444
上面是没有使用闭包的情况,下面来看使用闭包的情况
实例二:
1 def wrapper(step): 2 num = 1 3 4 def inner(): 5 nonlocal num # 引用num,与return inner形成闭包 6 num += step # 此时num在inner执行完了之后不会被清理,会作为下一次的起始值 7 print(num) 8 return inner 9 10 11 f = wrapper(3)12 j = 013 while j < 5:14 f()15 j += 1
执行结果
47101316
首先来看函数的结构,这是一个嵌套函数,wrapper函数里面嵌套一个inner函数,要知道,在函数外面是不能直接调用内层函数的,那么我们怎么做呢?答案是通过return,我们可以把外层函数的返回值设置为内层函数名,这样层层返回,就可以在外界调用任意位置的内层函数。可是这和我闭包又有什么关系呢?当然有啦,比如说我想调用inner函数对num进行操作,那么我是不是得让inner的外层函数wrapper的返回值设置为inner啊,其次还得在inner内部设置nonolocal,要不然就不能对num就行操作,其实在内层函数引用外层函数的变量,然后外层函数返回内层函数这样就形成了闭包,总结一下,关于闭包:
1. 闭包是内层函数对外层函数(非全局)变量的引用
2. 闭包只存在于内层函数中
3. 函数都要逐层返回,最终返回给最外层函数
下面来看一个例子,
1 def func(n): # 相当于n=name2 def inner():3 print(n)4 5 return inner6 7 8 name = "Hanser"9 f = func(name)
这个是不是闭包呢,答案是肯定的,这里的n传入func里面后是存放于func的名称空间里的,inner的print(n)就是内层函数对外层函数的引用,然后func的返回值是内层函数名inner,所以这个是闭包。这样判断是不是有点麻烦啊,那么有没有简单的办法呢,有的,python提供了判断闭包的方法: __closure__,来看代码
1 def func1(a): 2 n = 1 3 4 def func2(): 5 nonlocal n 6 n += a 7 8 def func3(): 9 nonlocal n10 n *= a11 print(n)12 return func313 return func214 15 16 f = func1(3) # f = func217 print(f.__closure__[0].cell_contents) # 获取引用的外层函数的变量,如果能获取到,就是闭包18 print(f.__closure__[1].cell_contents) #19 # print(f.__closure__[2].cell_contents) # 报错,没有第三个20 21 print("------我是华丽丽的分割线-------")22 23 f1 = func1(3)() # f1 = func324 print(f1.__closure__[0].cell_contents)25 print(f1.__closure__[1].cell_contents)26 # print(f1.__closure__[2].cell_contents) # 报错,没有第三个
执行结果
31------我是华丽丽的分割线-------34
总结一下,判断步骤:
(1)找到要判断的内层函数(line16和line23)
(2)获取该内层函数引用的外层函数的变量
(3)能获取到,是闭包;否则不是闭包
这里有一个小知识点:获取到的引用的外层函数的变量顺序: 传入的参数 > 外层函数定义的变量
闭包作用
正常程序执行时,遇到函数,随着函数的结束而关闭临时名称空间,闭包的本质就是闭包会创建一个空间,这个空间不会随着函数的结束而关闭,因而之后可以继续调用,这是非常有用的,闭包的应用场景:
1. 装饰器
2. 爬虫
来看一个爬虫实例
1 from urllib.request import urlopen 2 3 4 def but(): 5 content = urlopen("https://book.douban.com/annual/2018?source=navigation#1").read() # 获取网页源代码 6 7 def get_content(): 8 return content 9 return get_content10 11 12 fn = but()13 print(id(fn())) # 250570623153614 print(id(fn())) # 2505706231536 两个id一样,证明第一次获取的内容没有消失,第二次是直接调用第一次的内容15 content1 = fn() # 获取内容16 print(content1.decode("utf-8")) # 解码
执行结果
25057062315362505706231536豆瓣2018年度读书榜单
content内容不会随着but函数的结束而消失,这个非常有用,因为获取内容后还要进行筛选等操作,而请求一次因为网络延时等原因是非常耗时间的,有了闭包,就不用再次去请求获取内容,节省了很多时间。
迭代器
在讲迭代器之前,先来看一下可迭代对象,什么是可迭代对象呢,可迭代对象的定义是:内部含有__iter__方法的对象。
判断方法
方法一:
s1 = "hello"print(dir(s1)) # 返回的方法里面有__iter__()就是可迭代对象print("__iter__" in dir(s1)) # True
执行结果
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']True
f = open("goods.txt", encoding="utf-8", mode="r")print(dir(f))print("__iter__" in dir(f))
执行结果
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']True
方法二:
1 from collections import Iterable2 from collections import Iterator3 l1 = [1, 2, 3]4 print(isinstance(l1, Iterable)) # 判断是否是可迭代对象5 print(isinstance(l1, Iterator)) # 判断是否是迭代器
执行结果
TrueFalse
那么什么是迭代器呢,迭代器在可迭代对象的基础上增加了__next__方法(取值用),可迭代对象可以转化成迭代器,你猜的没错,就是用__iter__方法
1 obj = s1.__iter__() # 方法一:可迭代对象转化成迭代器2 # obj = iter(s1) # 方法二:可迭代对象转化成迭代器3 print(obj.__next__()) # a4 print(obj.__next__()) # b5 print(obj.__next__()) # c6 print(obj.__next__()) # d7 print(obj.__next__()) # 报错 StopIteration
执行结果
abcd
分析上述代码可以发现规律,__next__方法每次只取一个值,当取值个数超出时会报错,此外__next__()方法可以用next()方法代替。
1 s2 = [1, 2, 3]2 obj = s2.__iter__()3 print(obj.__next__())4 print(next(obj)) # 与__next__一样
执行结果
12
运用迭代器和while循环还可以模拟for循环,来看代码
1 lst = [1, 2, 3, 4, 5]2 obj = iter(lst)3 while True:4 try:5 print(next(obj))6 except StopIteration:7 break
执行结果
12345
试试字典
1 dic = { "name": "hanser", "age": 18, "height": 148}2 obj = iter(dic)3 while True:4 try:5 print(next(obj))6 except StopIteration:7 break
执行结果
nameageheight
对字典直接取值取出的是key,如果想取value或者键值对把dic换成dic.values()或dic.items()就行
1 dic = { "name": "hanser", "age": 18, "height": 148}2 obj = iter(dic.values())3 while True:4 try:5 print(next(obj))6 except StopIteration:7 break
执行结果
hanser18148
总结一下:
可迭代对象:内部含有__iter__方法的对象
str, list, tuple, dic, set, range(), 文件句柄都是可迭代对象
迭代器:内部含有__iter__方法和__next__方法的对象
str, list, tuple, dic, set, range()不是迭代器
文件句柄是迭代器
判断可迭代对象和迭代器的方法
"__iter__" in dir(s), "__next__" in dir(s)
isinstance(s, Iterable), isinstance(s, Iterator)
可迭代对象转化成迭代器
__iter__(), iter()
迭代器特点
节省内存,迭代器只存放下一个对象的值
惰性机制,next一次取一个值
单项取值,不走回头路
利用迭代器和while循环可以模拟for循环