昨天忽然在某羣看到這麼一段對話: 編程
jb關注的重點是生成器
、閉包
、裝飾器
、元編程
,顯然,做爲一位測試同窗,會點py,可讓飯更香,可是,若是簡歷上寫着精通py,那是否真的懂這些呢?bash
反正,jb是不懂的,若有錯誤,歡迎交流,大神請輕噴; 閉包
要說生成器,得先知道生成器是解決什麼問題的;app
相信你們都用過列表,假如list裏面有100W個元素,而只須要前面幾個元素,而list對象會一次性把全部元素都加載到內存,這樣就會形成後面的元素所佔的內存空間是白白浪費的;函數
那有沒有方案解決這問題?有,那就是迭代器;測試
迭代器,顧名思義就是用來迭代操做的對象,跟list同樣,能夠迭代獲取每個元素,跟list區別在於,構建迭代器的時候,不像列表把全部元素一次性加載到內存,而是以一種延遲計算(lazy evaluation)方式返回元素;ui
而迭代器有兩個基本的方法:inter()和next();lua
實現了__iter__
和__next__
方法的對象都稱爲迭代器,在調用next()
的時候返回下一個值,若是容器中沒有更多元素了,則拋出StopIteration異常;spa
import sys # 引入 sys 模塊
list=[1,2,3,4]
it = iter(list) # 建立迭代器對象
while True:
try:
print (next(it))
except StopIteration:
sys.exit()
複製代碼
在Python
中,使用了yield
的函數被稱爲生成器;debug
在調用生成器運行的過程當中,每次遇到 yield
時函數會暫停並保存當前全部的運行信息,返回 yield
的值, 並在下一次執行 next()
方法時從當前位置繼續運行;
例子,生成器:
def jb(N):
for i in range(N):
yield i**2
for item in jb(5):
print(item)
複製代碼
普通函數:
def jb(N):
res = []
for i in range(N):
res.append(i*i)
return(res)
for item in jb(5):
print(item)
複製代碼
做用域是程序運行時變量可被訪問的範圍,定義在函數內的變量是局部變量,局部變量的做用範圍只能是函數內部範圍內,它不能在函數外引用;
def foo():
num = 10 # 局部變量
print(num) # NameError: name 'num' is not defined
複製代碼
定義在模塊最外層的變量是全局變量,它是全局範圍內可見的,固然在函數裏面也能夠讀取到全局變量的。例如:
num = 10 # 全局變量
def foo():
print(num) # 10
複製代碼
函數不只能夠定義在模塊的最外層,還能夠定義在另一個函數的內部,像這種定義在函數裏面的函數稱之爲嵌套函數;
def print_msg():
# print_msg 是外圍函數
msg = "jb is here"
def printer():
# printer是嵌套函數
print(msg)
printer()
# 輸出 jb is here
print_msg()
複製代碼
閉包的概念就是當咱們在函數內定義一個函數時,這個內部函數使用了外部函數的臨時變量,且外部函數的返回值是內部函數的引用時,稱之爲閉包;
複製代碼
# 一個簡單的實現計算平均值的代碼
def get_avg():
scores = [] # 外部臨時變量
def inner_count_avg(val): # 內部函數,用於計算平均值
scores.append(val) # 使用外部函數的臨時變量
return sum(scores) / len(scores) # 返回計算出的平均值
return inner_count_avg # 外部函數返回內部函數引用
avg = get_avg()
print(avg(10)) # 10
print(avg(11)) # 10.5
複製代碼
相加的例子:
def adder(x):
def wrapper(y):
return x + y
return wrapper
# adder5對象是adder返回的閉包對象
adder5 = adder(5)
# 輸出 15
adder5(10)
# 輸出 11
adder5(6)
複製代碼
閉包的實際使用,大多數是用於裝飾器,而這是什麼東西?
假設程序實現了say_hello()
和say_goodbye()
兩個函數;
def say_hello():
print "hello!"
def say_goodbye():
print "hello!" # bug here
if __name__ == '__main__':
say_hello()
say_goodbye()
複製代碼
可是在實際調用中,發現程序出錯了,上面的代碼打印了兩個hello,通過調試發現是say_goodbye()
出錯了;
負責人要求調用每一個方法前都要記錄進入函數的名稱,好比這樣:
Copy
[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!
複製代碼
好,小A是個畢業生,他是這樣實現的。
def say_hello():
print "[DEBUG]: enter say_hello()"
print "hello!"
def say_goodbye():
print "[DEBUG]: enter say_goodbye()"
print "hello!"
if __name__ == '__main__':
say_hello()
say_goodbye()
複製代碼
很low吧? 嗯是的;
小B工做有一段時間了,他告訴小A能夠這樣寫;
def debug():
import inspect
caller_name = inspect.stack()[1][3]
print "[DEBUG]: enter {}()".format(caller_name)
def say_hello():
debug()
print "hello!"
def say_goodbye():
debug()
print "goodbye!"
if __name__ == '__main__':
say_hello()
say_goodbye()
複製代碼
這樣處理好多了,可是呢,仍是有問題,由於每一個業務函數都須要調用一下debug()
函數,並且若是之後說某個函數不能使用debug
函數,豈不是gg了?
這時候,就須要裝飾器了;
裝飾器本質上是一個Python函數,它可讓其餘函數在不須要作任何代碼變更的前提下增長額外功能,裝飾器的返回值也是一個函數對象;有了裝飾器,咱們就能夠抽離出大量與函數功能自己無關的雷同代碼並繼續重用;
複製代碼
原始版本
def debug(func):
def wrapper():
print "[DEBUG]: enter {}()".format(func.__name__)
return func()
return wrapper
@debug
def say_hello():
print "hello!"
複製代碼
這是最簡單的裝飾器,可是有一個問題,若是被裝飾的函數須要傳入參數,那麼這個裝飾器就壞了。由於返回的函數並不能接受參數,能夠指定裝飾器函數wrapper接受和原函數同樣的參數,好比:
def debug(func):
def wrapper(something): # 指定一毛同樣的參數
print "[DEBUG]: enter {}()".format(func.__name__)
return func(something)
return wrapper # 返回包裝過函數
@debug
def say(something):
print "hello {}!".format(something)
複製代碼
這樣就解決了一個問題,但又多了N個問題;
由於函數有千千萬,只管本身的函數,別人的函數參數是什麼樣子,鬼知道?還好Python提供了可變參數*args
和關鍵字參數**kwargs
,有了這兩個參數,裝飾器就能夠用於任意目標函數了;
def debug(func):
def wrapper(*args, **kwargs): # 指定宇宙無敵參數
print "[DEBUG]: enter {}()".format(func.__name__)
print 'Prepare and say...',
return func(*args, **kwargs)
return wrapper # 返回
@debug
def say(something):
print "hello {}!".format(something)
# @debug的意思是,執行debug函數,而傳入的參數就是下方緊接的sya函數;
複製代碼
1)裝飾器的做用就是爲已經存在的函數或對象添加額外的功能;
2)閉包函數的必要條件:
3)生成器是一個返回迭代器的函數,只能用於迭代操做,每次遇到 yield
時函數會暫停並保存當前全部的運行信息,返回 yield
的值, 並在下一次執行 next()
方法時從當前位置繼續運行;