文章目錄
前言函式的不定長引數lambda函式建立包生成器什麼是生成器為什麼要有生成器如何創造一個生成器把一個列表推導式的[]改成()yield關鍵字生成器的工作原理程式碼示例裝飾器巢狀函式前言
時隔20天,也該給這個系列畫上一個句號啦。後期我會對這個系列進行一個整合,預計會整理為13篇,不用擔心,是原地整理,一般不會刪部落格。
這一篇不是爬蟲相關,是我重新梳理了Python中的相關知識之後,發現了一些和C++異同之處,覺得應該再整理一份。
函式的不定長引數
有的時候,你會需要一個函式去處理比它預設時更多的引數,這時候如果沒有預先佔位,就比較尷尬了。
python自定義函式中有兩中不定長引數,第一種是*name,第二種是**name。加了星號 * 的引數會以元組(tuple)的形式匯入,存放所有未命名的變數引數。加了兩個星號 ** 的引數會以字典的形式匯入。
第一種形式的不定長引數,在傳入額外的引數時可以不用指明引數名,直接傳入引數值即可,第二種因為返回的是字典,所以傳入時需要指定引數名。
下面是兩個簡單的栗子:
def funA(a, b, *args): print(a) print(b) print(args) funA(1, 2, 3, 5, 6, 7)輸出如下:12(3, 5, 6, 7)123456789101112
def funB(a, b, **vardict): print(a) print(b) print(vardict) print(vardict['l']) funB(1, 2, l=3, m=4)輸出結果如下:12{'l': 3, 'm': 4}312345678910111213
至於它的真實應用場景,小白是用不上了,不過你們以後要往深了走肯定是要碰上的。
有興趣的話可以看一下C++版的:argc和argv的妙用
lambda函式
匿名函式lambda:是指一類無需定義識別符號(函式名)的函式或子程式。lambda 函式可以接收任意多個引數 (包括可選引數) 並且返回單個表示式的值。
說明:我將它們用在需要封裝特殊的、非重用程式碼上,避免令我的程式碼充斥著大量單行函式。
程式碼示例:
p = lambda x,y:x+yprint(p(4,6))12
a=lambda x:x*xprint(a(3)) # 注意:這裡直接a(3)可以執行,但沒有輸出的,前面的print不能少 12
a = lambda x,y,z:(x+8)*y-zprint(a(5,6,8)12
在C++當中這叫做宏定義函式,各位不要懼怕C++,更不要去排斥,如果還年輕,要在程式設計師這一行越走越遠,C++是個不錯的首選。我們來看一下C++中的實現:C++ #define
建立包
包其實就是資料夾,更確切的說,是一個包含“init.py”檔案的資料夾。
因此,如果我們想手動建立一個包,只需進行以下 2 步操作:
1、新建一個資料夾,資料夾的名稱就是新建包的包名;2、在該資料夾中,建立一個 __init__.py 檔案(前後各有 2 個下劃線‘_’),該檔案中可以不編寫任何程式碼。當然,也可以編寫一些 Python 初始化程式碼,則當有其它程式檔案匯入包時,會自動執行該檔案中的程式碼12
生成器
什麼是生成器
在Python中,一邊迴圈一邊計算的機制,稱為生成器:generator。
為什麼要有生成器
列表所有資料都在記憶體中,如果有海量資料的話將會非常耗記憶體。
如:僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
如果列表元素按照某種演算法推算出來,那我們就可以在迴圈的過程中不斷推算出後續的元素,這樣就不必建立完整的list,從而節省大量的空間。
簡單一句話:我又想要得到龐大的資料,又想讓它佔用空間少,那就用生成器!
如何創造一個生成器
把一個列表推導式的[]改成()
L = [x * x for x in range(10)][0, 1, 4, 9, 16, 25, 36, 49, 64, 81]g = (x * x for x in range(10))<generator object <genexpr> at 0x1022ef630>12345
yield關鍵字
如果一個函式中包含yield關鍵字,那麼這個函式就不再是一個普通函式,而是一個generator。呼叫函式就是建立了一個生成器(generator)物件。
生成器的工作原理
(1)生成器(generator)能夠迭代的關鍵是它有一個next()方法,工作原理就是透過重複呼叫next()方法,直到捕獲一個異常。
(2)帶有 yield 的函式不再是一個普通函式,而是一個生成器generator。可用next()呼叫生成器物件來取值。next 兩種方式 t.next() | next(t)。(基本上不會用next()來獲取下一個返回值,而是直接使用for迴圈來迭代)。
(3)yield相當於 return 返回一個值,並且記住這個返回的位置,下次迭代時,程式碼從yield的下一條語句開始執行。
(4).send() 和next()一樣,都能讓生成器繼續往下走一步(下次遇到yield停),但send()能傳一個值,這個值作為yield表示式整體的結果
換句話說,就是send可以強行修改上一個yield表示式值。比如函式中有一個yield賦值,a = yield 5,第一次迭代到這裡會返回5,a還沒有賦值。第二次迭代時,使用.send(10),那麼,就是強行修改yield 5表示式的值為10,本來是5的,那麼a=10。
程式碼示例
來看一段楊輝三角的程式碼示例:
def triangle(): _list, new_list = [], [] while True: length = len(_list) if length == 0: new_list.append(1) else: for times in range(length + 1): if times == 0: new_list.append(1) elif times == length: new_list.append(1) else: temp = _list[times - 1] + _list[times] new_list.append(temp) yield new_list #返回值,然後掛起函式,等待下一次呼叫 _list = new_list.copy()#呼叫後會繼續執行下去 new_list.clear()n = 0for result in triangle(): n += 1 print(result) if n == 10: break12345678910111213141516171819202122232425
[1][1, 1][1, 2, 1][1, 3, 3, 1][1, 4, 6, 4, 1][1, 5, 10, 10, 5, 1][1, 6, 15, 20, 15, 6, 1][1, 7, 21, 35, 35, 21, 7, 1][1, 8, 28, 56, 70, 56, 28, 8, 1][1, 9, 36, 84, 126, 126, 84, 36, 9, 1]12345678910
如果覺得太難啦,那就換一個斐波那契吧哈哈哈哈
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1# 拿去列印ab看看123456789
生成器這個東西嘛,說實在的,學C++兩年來沒見過類似的。
裝飾器
一看這個名字,我就想到了裝飾者模式。
Python的裝飾器(decorator)可以說是Python的一個神器,它可以在不改變一個函式程式碼和呼叫方式的情況下給函式新增新的功能。
牛逼都吹成這樣了,我也不想多說什麼,直接上程式碼吧:
import timedef time_it(func): def inner(): start = time.time() func() end = time.time() print('用時:{}秒'.format(end-start)) return inner@time_itdef func1(): time.sleep(2) print("Func1 is running.")if __name__ == '__main__': func1()1234567891011121314151617
能看懂不?怪我,我的裝飾者模式寫了兩篇,也沒寫出精華來,就不能放連結給你們了。
看了裝飾器,我才知道裝飾著模式的精妙之處,強!!!
執行結果:
Func1 is running.用時:2.0056326389312744123
巢狀函式
講到裝飾器,就不得不講一下內函式(我也不知道為什麼,每本書上都是這麼說的)
我們先來看一個最簡單的巢狀函式的例子。
def outer(): x = 1 def inner(): y = x + 1 print(y) inner()outer() #輸出結果 212345678
可曾有見過這類函式 ?
def outer(): x = 1 def inner(): y = x + 1 print(y) return innerouter() # 輸出<function outer.<locals>.inner at 0x039248E8>f1 = outer()f1() # 輸出212345678910
上面那倆比較簡單,我們來看個抽象的:
def decorator(func): def inner(*args, **kwargs): add_other_actions() return func(*args, **kwargs) return inner12345
能轉的過彎嗎?
這裡就要用到裝飾器了。
我們現在可以開始動手寫個名為hint的裝飾器了,其作用是在某個函式執行前給我們提示。這裡外函式以hint命名,內函式以常用的wrapper(包裹函式)命名。
def hint(func): def wrapper(*args, **kwargs): print('{} is running'.format(func.__name__)) return func(*args, **kwargs) return wrapper@hintdef hello(): print("Hello!")123456789
我們現在對hello已經進行了裝飾,當我們呼叫hello()時,我們可以看到如下結果。
hello() hello is running.Hello!1234
值得一提的是被裝飾器裝飾過的函式看上去名字沒變,其實已經變了。當你執行hello()後,你會發現它的名字已經悄悄變成了wrapper,這顯然不是我們想要的。這一點也不奇怪,因為外函式返回的是由wrapper函式和其外部引用變數組成的閉包。
為了解決這個問題保證裝飾過的函式__name__屬性不變,我們可以使用functools模組裡的wraps方法,先對func變數進行wraps。
from functools import wrapsdef hint(func): @wraps(func) def wrapper(*args, **kwargs): print('{} is running'.format(func.__name__)) return func(*args, **kwargs) return wrapper@hintdef hello(): print("Hello!")12345678910111213
這是一個通用裝飾器的模板,要不要收藏起來就看你個人了。
就算你不想看,我也要再放一個高階的裝飾器通用模板,因為我自己早晚用得上:
from functools import wrapsdef hint(coder): def wrapper(func): @wraps(func) def inner_wrapper(*args, **kwargs): print('{} is running'.format(func.__name__)) print('Coder: {}'.format(coder)) return func(*args, **kwargs) return inner_wrapper return wrapper@hint(coder="John")def hello(): print("Hello!")123456789101112131415
這就叫做:書又不是讀給別人的。
類裝飾器也一併放這兒了:
from functools import wraps#類的裝飾器寫法, 不帶引數class Hint(object): def __init__(self, func): self.func = func @wraps(func) def __call__(self, *args, **kwargs): print('{} is running'.format(self.func.__name__)) return self.func(*args, **kwargs)#類的裝飾器寫法, 帶引數class Hint(object): def __init__(self, coder=None): self.coder = coder def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('{} is running'.format(func.__name__)) print('Coder: {}'.format(self.coder)) return func(*args, **kwargs) # 正式呼叫主要處理函式 return wrapper
裝飾器部分到此告一段落。