JB的Python之旅-生成器、閉包、裝飾器

原因

昨天忽然在某羣看到這麼一段對話: 編程

image.png-110kB
image.png-88.8kB
image.png-95.9kB

jb關注的重點是生成器閉包裝飾器元編程,顯然,做爲一位測試同窗,會點py,可讓飯更香,可是,若是簡歷上寫着精通py,那是否真的懂這些呢?bash

反正,jb是不懂的,若有錯誤,歡迎交流,大神請輕噴; 閉包

image.png-881.8kB

生成器 & 迭代器

要說生成器,得先知道生成器是解決什麼問題的;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() 方法時從當前位置繼續運行;

相關文章
相關標籤/搜索