函數名的運用、閉包以及叠代器
函數名的運用
函數名是一種特殊的變量,函數名加上括號後表示函數執行,除此之外,函數名還可以進行如下幾條操作:
1. 作為變量賦值
1 def func(): 2 print(666) 3 4 5 f1 = func 6 f2 = f1 7 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()
執行結果
111 222 333
方法一裏面是按照順序執行函數,如果我們指定要執行某些函數呢,來看方法二
方法二:
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: func3 17 } 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 x 3 4 5 def func1(): 6 print("in func1") 78 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
執行結果
4 4 4 4 4
上面是沒有使用閉包的情況,下面來看使用閉包的情況
實例二:
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 = 0 13 while j < 5: 14 f() 15 j += 1
執行結果
4 7 10 13 16
首先來看函數的結構,這是一個嵌套函數,wrapper函數裏面嵌套一個inner函數,要知道,在函數外面是不能直接調用內層函數的,那麽我們怎麽做呢?答案是通過return,我們可以把外層函數的返回值設置為內層函數名,這樣層層返回,就可以在外界調用任意位置的內層函數。可是這和閉包又有什麽關系呢?當然有啦,比如說我想調用inner函數對num進行操作,那麽我是不是得讓inner的外層函數wrapper的返回值設置為inner啊,其次還得在inner內部設置nonolocal,要不然就不能對num就行操作,其實在內層函數引用外層函數的變量,然後外層函數返回內層函數這樣就形成了閉包,總結一下,關於閉包:
1. 內層函數對外層函數(非全局)變量的引用
2. 閉包只存在於內層函數中
3. 函數都要逐層返回,最終返回給最外層函數
下面來看一個例子,
1 def func(n): # 相當於n=name 2 def inner(): 3 print(n) 4 5 return inner 6 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 n 10 n *= a 11 print(n) 12 return func3 13 return func2 14 15 16 f = func1(3) # f = func2 17 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 = func3 24 print(f1.__closure__[0].cell_contents) 25 print(f1.__closure__[1].cell_contents) 26 # print(f1.__closure__[2].cell_contents) # 報錯,沒有第三個
執行結果
3 1 ------我是華麗麗的分割線------- 3 4
總結一下,判斷步驟:
(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_content 10 11 12 fn = but() 13 print(id(fn())) # 2505706231536 14 print(id(fn())) # 2505706231536 兩個id一樣,證明第一次獲取的內容沒有消失,第二次是直接調用第一次的內容 15 content1 = fn() # 獲取內容 16 print(content1.decode("utf-8")) # 解碼
執行結果
2505706231536 2505706231536 <!doctype html> <html lang="zh-cmn-Hans"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"> <meta name="apple-mobile-web-app-capable" content="yes"> <link rel="shortcut icon" href="https://img3.doubanio.com/favicon.ico"> <meta name="format-detection" content="telephone=no"> <meta name="url_name" content="book_annual2018"> <meta name="user_id" content=""> <meta property="og:site_name" content="豆瓣" /> <meta property="og:title" content="豆瓣2018年度讀書榜單" /> <meta property="og:description" content="這一年不可錯過的好書都在這裏了" /> <meta property="og:url" content="https://book.douban.com/annual/2018?source=broadcast" /> <meta property="og:image" content="https://img3.doubanio.com/img/files/file-1545618075.jpg" /> <title>豆瓣2018年度讀書榜單</title> <script> window.ITHIL = {}; ITHIL.isFrodo = ‘False‘ === ‘True‘; ITHIL.isWechat = ‘False‘ === ‘True‘; </script> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); var hash = ‘2018‘ === ‘2018‘ ? ‘6e5dcf7c287704f738c7febc2283cf0c‘ : ‘16a14f3002af32bf3a75dfe352478639‘ hm.src = "https://hm.baidu.com/hm.js?" + hash; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> </head> <body> <div id="app"></div> <script src="https://img3.doubanio.com/f/ithil/31683c94fc5c3d40cb6e3d541825be4956a1220d/js/lib/es5-shim.min.js"></script> <script src="https://img3.doubanio.com/f/ithil/a7de8db438da176dd0eeb59efe46306b39f1261f/js/lib/es6-shim.min.js"></script> <script src="https://img3.doubanio.com/dae/cdnlib/libs/jweixin/1.0.0/jweixin.js"></script> <script src="https://img3.doubanio.com/f/ithil/b92012acc8222b31e7f1307c154fdb90b56d64d1/gen/ithil2018.bundle.js"></script> <div alt="main-pic" style="display: none"> <img type="hidden" alt="cover" src="https://img3.doubanio.com/img/files/file-1545618075.jpg"> </div> </body> </html>
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 Iterable 2 from collections import Iterator 3 l1 = [1, 2, 3] 4 print(isinstance(l1, Iterable)) # 判斷是否是可叠代對象 5 print(isinstance(l1, Iterator)) # 判斷是否是叠代器
執行結果
True
False
那麽什麽是叠代器呢,叠代器在可叠代對象的基礎上增加了__next__方法(取值用),可叠代對象可以轉化成叠代器,你猜的沒錯,就是用__iter__方法
1 obj = s1.__iter__() # 方法一:可叠代對象轉化成叠代器 2 # obj = iter(s1) # 方法二:可叠代對象轉化成叠代器 3 print(obj.__next__()) # a 4 print(obj.__next__()) # b 5 print(obj.__next__()) # c 6 print(obj.__next__()) # d 7 print(obj.__next__()) # 報錯 StopIteration
執行結果
a
b
c
d
分析上述代碼可以發現規律,__next__方法每次只取一個值,當取值個數超出時會報錯,此外__next__()方法可以用next()方法代替。
1 s2 = [1, 2, 3] 2 obj = s2.__iter__() 3 print(obj.__next__()) 4 print(next(obj)) # 與__next__一樣
執行結果
1 2
運用叠代器和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
執行結果
1 2 3 4 5
試試字典
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
執行結果
name
age
height
對字典直接取值取出的是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
執行結果
hanser
18
148
總結一下:
可叠代對象:內部含有__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循環
函數名的運用、閉包以及叠代器