(十一)函式名的使用,閉包,迭代器
⼀. 函式名的運用.
函式名是⼀個變數, 但它是⼀個特殊的變數, 與括號配合可以執⾏函式的變數.
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() # 執⾏上⾯返回的函式
總結一下,利用函式名可以做5件事:獲取函式記憶體地址,函式名可以當做變數一樣,被賦值,函式書名可以被列表等容器類裝起來,函式名可以被作為實參,傳遞給其他的函式,函式名可以作為函式的返回值。
⼆. 閉包
什麼是閉包? 閉包就是內層函式, 對外層函式(非全域性)的變數的引用叫閉包。
def func1(): name = "abc" def func2(): print(name) # 閉包 func2() func1() 結果: abc
我們可以使用__closure__來檢測函式是否是閉包。使用 函式名.__closure__返回cell就是閉包,返回None就不是閉包。
def func1(): name = "abc" def func2(): print(name) # 閉包 func2() print(func2.__closure__) # (<cell at 0x10c2e20a8: str object at0x10c3fc650>,) func1()
再來看一個問題, 如何在函式外邊調⽤內部函式呢?
def outer(): name = "abc" # 內部函式 def inner(): print(name) return inner fn = outer() # 訪問外部函式, 獲取到內部函式的函式地址 fn() # 訪問內部函式
那如果多層巢狀呢? 很簡單, 只需要⼀層⼀層的往外層返回就⾏了
def func1(): def func2(): def func3(): print("嘿嘿") return func3 return func2 func1()()()
看到這段程式碼蒙了嗎?反正我是不知道怎麼回事了,然後自己把這三個括號的程式碼,修改了一下,就理解了,看修改後的:
def func1(): def func2(): def func3(): print("嘿嘿") return func3 return func2 f2 = func1()#拿到了func2 f3 = f2()#拿到了func3 f3()#執行後結果與上面是一樣的
由它我們可以引出閉包的好處:由於我們在外界可以訪問內部函式,那這個時候內部函數訪問的時間和時機就不⼀定了, 因為在外部, 我可以選擇在任意的時間去訪問內部函式。這個時候想⼀想,我們之前說過, 如果⼀個函式執⾏完畢, 則這個函式中的變數以及區域性名稱空間中的內容都將會被銷燬。 在閉包中, 如果變數被銷燬了, 那內部函式將不能正常執⾏。所以, python規定, 如果你在內部函式中訪問了外層函式中的變數, 那麼這個變數將不會消亡,將會常駐在記憶體中。也就是說, 使用閉包可以保證外層函式中的變數在記憶體中常駐。 這樣做有什麼好處呢? 非常⼤的好處, 我們來看⼀個關於爬⾍的程式碼 :
from urllib.request import urlopen def but(): content = urlopen("http://www.xxxx.com").read()#自己選擇一個網站,但是不要是大公司的 def get_content(): return content return get_content fn = but() # 這個時候就開始載入xxxx網站的內容 # 後⾯需要⽤到這⾥⾯的內容就不需要再執⾏⾮常耗時的⽹絡連線操作了 content = fn() # 獲取內容 print(content) content2 = fn() # 重新獲取內容 print(content2)
說明:看到read()時,有沒有很熟悉,可以在檔案操作那裡就有在這個方法,所以可以嘗試一下read(3)加個數字,readline(),readlines()。
綜上, 閉包的作用就是讓⼀個變數能夠常駐記憶體,供後⾯的程式使⽤。
三. 迭代器
我們之前⼀直在用可迭代物件進⾏迭代操作. 那麼到底什麼是可迭代物件. 本⼩節主要討論可迭代物件. ⾸先我們先回顧⼀下⽬前我們所熟知的可迭代物件有哪些: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函式來檢視類中定義好的所有⽅法.
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__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode',
'endswith', 'expandtabs', 'find', 'formamat', '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']
我們發現在字串中可以找到__iter__. 繼續看⼀下list, tuple, dict, set。
print(dir(tuple))
print(dir(list))
print(dir(open("test.txt"))) # ⽂件物件
print(dir(set))
print(dir(dict))
其餘的型別可以列印一下看看,裡面是否也有__iter__。我們會發現這⼏個可以進⾏for迴圈的東⻄都有__iter__函式, 包括range也有. 可以⾃⼰試⼀下。
這是檢視⼀個物件是否是可迭代物件的第⼀種辦法. 我們還可以通過isinstance()函式來檢視⼀個物件是什麼型別的
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
綜上. 我們可以確定. 如果物件中有__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
接下來,使用while迴圈+迭代器來模擬for迴圈:
lst = [1,2,3] lst_iter = lst.__iter__() while True: try: i = lst_iter.__next__() print(i) except StopIteration: break
總結:
Iterable: 可迭代物件. 內部包含__iter__()函式
Iterator: 迭代器. 內部包含__iter__() 同時包含__next__().
迭代器的特點:
1. 節省記憶體.
2. 惰性機制
3. 不能反覆, 只能向下執行.