Python迭代器&生成器&裝飾器

1. 迭代器

1.1 可迭代對象(Iterator)

迭代器協議:某對象必須提供一個__next__()方法,執行方法要麼返回迭代中的下一項,要麼引起一個Stopiteration異常,以終止迭代(只能往後走,不能往前退)

協議是一種規範,可迭代對象實現了迭代器協議,python的內部工具(如for循環、sum、min、max函數),使用迭代器協議訪問對象

可迭代對象(Iterator):實現了迭代器協議的對象(如何實現:對象內部定義了一個__iter__()方法),也就是可迭代對象內部要包含__iter__() 函數

迭代器(Iterator):內部包含了__iter()__() ,同時也包含__next__()

1.2 for循環的工作機制

先通過__iter__()方法將可迭代對象轉換為迭代器,再調用迭代器中的__next__()方法遍歷,最後再抓取結束時的異常

print("解析for循環的工作機制:")
num = [1, 2, 3, 4, 5]
for i in num:           # 工作機制:  num.__iter__()  ------>  num.__next__()
    print(i)

1.3 可迭代對象與迭代器

1)可迭代對象與迭代器之間的關係

  • 可迭代對象包含迭代器
  • 如果一個對象擁有__iter__方法,其就是可迭代對象(可以被for循環迭代);如果一個對象擁有next方法,其是迭代器
  • 定義可迭代對象,必須事先__iter__方法;定義迭代器,必須實現__iter__和next方法。

2)迭代器的特點

  • 節省記憶體
  • 惰性機制
  • 不能反覆,只能向下執行

3)isinstance()判斷對象是否為Iterable對象:

from collections import Iterable
print(isinstance([], Iterable))

1.4 迭代器協議的使用示例

print("使用while循環遍歷一個列表:")
index = 0
while index < len(num):
    print(num[index])
    index += 1

print("利用迭代器協議遍歷一個列表:")
iter = num.__iter__()
print(iter.__next__())
print(iter.__next__())
print(iter.__next__())

print("解析文件操作中對於文件內容的遍歷:")
f = open("test.txt", "r")
f_iter = f.__iter__()      # 這裡先將整個文件轉換為一個迭代器,之後對迭代器調用__next__()方法,只在有需要的時候才載入文件一行內容
# 當取出一行內容時,因為沒有賦值給任何變數,所以佔用的記憶體會被python的自動回收機制回收,所以這種遍歷文件的方式只會動態的佔用一小塊記憶體
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
print(f_iter.__next__(), end="")
f.close()

print("next()方法:")     # next()方法--->調用__next__()方法
the_num = [1, 2, 3]
the_iter = the_num.__iter__()   # 也可以直接使用iter(the_num)方法
print(next(the_iter))     # next(the_iter)  ----->  調用 the_iter.__next__()

2. 生成器

2.1 生成器概述(generator)

生成器(generator)就是可迭代對象(它在內部實現了迭代器協議)

生成器在python中的兩種表達形式:

  • 生成器表達式
  • 生成器函數

觸發生成器的方式:

  • 通過調用__next__()方法,相當於send(None)
  • 通過調用send()方法

2.2 生成器函數

  • 只要函數中包含yield關鍵字,則此函數就是一個生成器函數
  • 每調用一次 __next__(),yield後面的值就會被返回一次,且保留此次的狀態,下一次調用從此位置開始

1)生成器函數

def func():
    print("現在開始執行生成器函數:")
    print("First----->")
    yield "第一步"
    print("Second----->")
    yield "第二步"
    print("Third")
    yield "第三步"
    print("End")
    yield "生成器函數調用完畢"

f = func()
print(f.__next__())    # __next__()函數調用接受到的返回值就是yield後面的值
print(f.__next__())
print(f.__next__())
print(f.__next__())

2)用yield方式的生成器實現字典中文件的內容查找

def get_num():
    f = open("population.txt", "r", encoding="utf-8")  # 這個文件中的每一行都是一個字典
    for i in f:
        yield i                                        # 這裡返回的 i 為字元串類型

population = eval(get_num().__next__())                # 這裡通過eval()函數重新恢復了文件中數據的數據類型,恢復成了字典
print("%s的人口數為%s" % (population["city"], population["population"]))
# 注意:生成器只能遍歷一次

# 用生成器表達式的方式實現字典中文件數字的求和
f = open("population.txt", "r", encoding="utf-8")
p = sum(eval(i)["population"] for i in f)
print(p)

3)send()方法的使用

def my_func():
    print("First")
    send1 = yield 1     # yield後面的值是next方法調用的返回值,而前面的值是yield接收值之後賦值的變數
    print("Second")
    print("此次send傳送的值為:", send1)
    send2 = yield 2
    print("Third")
    print("此次send傳送的值為:", send2)
    yield 3
    print("End")
  
m = my_func()           # 這裡並不會執行這個函數,因為只要函數內部定義了yield關鍵字,函數就變成了生成器,只有通過next方法調用
print(m.__next__())

print(m.send(None) )    # 這裡的send(None),就相當於__next__()方法,觸發一次生成器
# send()函數將值傳遞給當前停留位置的yield,之後這個yield會將值傳遞給前面的變數
print(m.send("這是第二次send"))

4)yield from

  • yield from可以直接把迭代對象中的每一個數據作為生成器的結果進行返回
  • 因為yield from是將列表中的每一個元素返回,所以寫兩個yield from並不會產生交替的效果
def func():
    lst1 = ['衛龍','老冰棍','北冰洋','牛羊配']
    lst2 = ['饅頭','花捲','豆包','大餅']
    yield from lst1
    yield from lst2
g = func()
for i in g:
    print(i)
    
# 衛龍
# 老冰棍
# 北冰洋
# 牛羊配
# 饅頭
# 花捲
# 豆包
# 大餅

5)額外

def func3():
    for i in range(10):
        yield i

t = func3()
# for i in t:            # 因為for循環的工作機制就是使用迭代器協議調用next方法,所以會觸發生成器函數
#     print(i)

li1 = (i for i in t)     # 這裡li1會得到一個生成器的地址
li2 = (i for i in li1)   
print(type(li1))
print(list(li1))         # 這裡的list函數中會用for循環機製取值,從而觸發生成器,現在這個位置li1中的值已經被取完了
print(list(li2))         # 因li1中的值已經被取完了,所以li2現在已經取不到值了

2.3 推導式

  • 推導式有:列表推導式、字典推導式、集合推導式
  • 沒有元組推導式

1)列表推導式

  • 三元表達式
    name = "hgzero"
    ret = "me" if name == "hgzero" else "other"    # 若name等於hgzero,則返回me,否則返回other
    print(ret)
  • 列表推導式(列表解析)
    list1 = [i for i in range(10)]                 # 列表解析會將生成的列表直接放於記憶體中,會佔用較大的記憶體
    print(list1)
    list2 = ["數字%d" % i for i in range(10)]       # for循環處理後將i依次往前面傳遞
    print(list2)
  • 篩選模式(三元表達式和列表解析的使用示例)
    print("三元表達式和列表解析結合")
    list3 = ["數字%d" % i for i in range(10) if i > 5]   # 這裡不能使用else,因為已經構成了三元表達式了
    print(list3)

2)生成器推導式

  • 將列表推導式外圍的中括弧換成小括弧就變成了一個生成器推導式
  • 生成器推導式也可以使用篩選模式
print("生成器表達式:")
list2 = ("數字%d" % i for i in range(10))    # 將列表解析表達式外圍的中括弧換成小括弧就變成了一個生成器
# 生成器表達式更節省記憶體
print(list2)
print(list2.__next__())
print(list2.__next__())
print(list2.__next__())
print(list2.__next__())

# 可以將生成器表達式傳遞給sum()等函數,以節省佔用記憶體的大小
print(sum(i for i in range(100000)))        
# 這裡的sum函數裡面的生成器表達式不需要加括弧
# 如果直接在sum中傳入一個非常大的列表,會造成佔用太多記憶體而導致機器卡死,而用生成器的方式傳入則會動態的佔用很小的一片記憶體

3)字典推導式

lst1 = ['jay','jj','huazai']
lst2 = ['周杰倫','林俊傑','劉德華']
dic = {lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)

4)集合推導式

  • 集合推導式可以直接生成一個集合
  • 集合的特點:無需、不重複,所以集合推導式自帶去重功能
lst = [1,2,3,-1,-3,-7,9]
s = {abs(i) for i in lst}
print(s)

2.4 生產者消費者模型

import time

# def producer():
#     ret = []
#     for i in range(100):
#         time.sleep(0.1)
#         ret.append("包子%s" % i)
#     return ret
#
# def consumer(ret):
#     for index, baozi in enumerate(ret):
#         time.sleep(0.1)
#         print("第%d個人來了,吃了第%s個包子" % (index, baozi))
#
# p = producer()
# consumer(p)

def producer():
     c1 = consumer("hgzero")     
     c2 = consumer("wuzhihao")
     c1.__next__()
     c2.__next__()
     for i in range(10):
         time.sleep(1)
         print("現在包子 %s 已經做好了!" % i)
         c1.send("第%s個包子" % i)
         c2.send("第%s個包子" % i)

def consumer(name):
    print("吃包子開始!")
    while True:
        baozi = yield
        time.sleep(1)
        print("我是 %s , 現在開始吃 %s 包子" % (name, baozi))

producer()

2.5 閉包

在一個外函數中定義了一個內函數,內函數里運用了外函數的臨時變數,並且外函數的返回值是內函數的引用,這樣就構成了一個閉包。簡單來說,閉包就是內層函數,對外層函數變數的引用。

一般情況下,如果一個函數執行結束,則這個函數的內部的變數以及局部命名空間中的內容都會被釋放掉。但是在閉包中,如果外函數在結束的時候發現有自己的臨時變數將來會在內部函數中用到,就把這個臨時變數綁定給了內部函數,然後自己再結束。也就說,使用閉包,可以保證外部函數中的變數在記憶體中常駐,以供後面的程式使用。

其實閉包中外部函數的返回值定義成內部函數的地址,是為了防止內部函數被垃圾回收機制回收。

def func1():  
    def func2(): 
        s = '地主家的傻兒子'
        def func3():       
            print(s)      
        return func3 
    return func2
func1()()()

2.6 解壓序列

# 解壓序列:
a, *b, c = [1, 2, 3, 4, 5, 6, 7, 8, 9]    # 這裡的 * 號表示所有的意思
print(a)
print(b)
print(c)

# 交換兩個變數的值:
a = 1
b = 2
print("通過中間變數的形式交換:")
c = a
a = b
b = c
print(a, b)

print("另外兩種不引入額外變數實現交換的方式:")
f1 = 1
f2 = 2
f1, f2 = f2, f1
print(f1, f2)

n1 = 1
n2 = 2
n1 = n1 + n2
n2 = n1 - n2
n1 = n1 - n2
print(n1, n2)

3. 裝飾器

3.1 裝飾器概述

  • 裝飾器:本質上就是函數,功能是為其他函數添加附加功能
  • 原則:
    • 不修改被修飾函數的源程式碼
    • 不修改被修飾函數的調用方法
  • 裝飾器 = 高階函數 + 函數嵌套 + 閉包

3.2 裝飾器的實現方式

1)用高階函數的方式實現裝飾器的功能:不合格,因為這種方式會使工作函數調用了兩次

#    1.不修改被修飾函數的源程式碼
#    2. 不修改被修飾函數的調用方式

# 裝飾器 = 高階函數+函數嵌套+閉包
import time

# 用高階函數的形式想實現裝飾器的功能,不合格,因為這種方式會使工作函數調用了兩次
def work_func():
    time.sleep(1)
    print("這裡是work_func函數")

def dec_func(func):
    start_time = time.time()
    func()
    finish_time = time.time()
    print("這個函數運行的時間是%d" % (finish_time-start_time))
    return func

work_func = dec_func(work_func)   # 這裡在賦值之前就已經執行了dec_func(work_func)函數
work_func()                   # 這裡又執行了一便work_func函數

2)用高階函數 + 函數嵌套 + 閉包的方式實現裝飾器

import time
def work_func2():
    time.sleep(1)
    print("這裡是work_func2函數")

def dec_func2(func):
    def wapper():
        start_time = time.time()
        func()
        finish_time = time.time()
        print("這個函數運行的時間是%d" % (finish_time - start_time))
    return wapper

work_func2 = dec_func2(work_func2)   # 這裡執行dec_func2函數時遭遇一個閉包,得到的是閉包wrapper函數的記憶體地址(並沒有執行它)
work_func2()                         # 這裡執行上面得到的閉包函數,即執行wapper()函數

3)用「語法糖」的形式實現裝飾器

import time
def dec_func3(func):
    def wapper():
        start_time = time.time()
        func()
        finish_time = time.time()
        print("這個函數運行的時間是%d" % (finish_time - start_time))
    return wapper

@dec_func3     # 用「語法糖」的形式實現裝飾器 ,相當於 work_func3 = dec_func3(work_func3)
def work_func3():
    time.sleep(1)
    print("這裡是work_func3函數")
work_func3()   


def dec_func4(func):   # 裝飾器改進,添加返回值和多參數功能
    def wapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)  # func()函數的返回值就是work_func函數中的返回值,用res接收之後,再返回出去
        finish_time = time.time()
        print("這個函數運行的時間是%d" % (finish_time - start_time))
        return res
    return wapper

@dec_func4     # work_func4 = dec_func3(work_func3) 這時執行work_func4函數就是執行wapper函數,因為dec_func3函數返回的就是wapper的地址
def work_func4(name, age):
    time.sleep(1)
    print("這裡是work_func4函數")
    print("Name:【%s】,Age:【%d】" % (name, age))
    return 12345                      # 這時,work_func函數的返回值就是dec_func函數的閉包---> wapper函數的返回值
work_func4("hgzero", 21)

3.3 帶認證功能的裝飾器

import time

user = [
    {"username": "hgzero", "passwd": "123"},
    {"username": "wzh", "passwd": "123"},
    {"username": "hg", "passwd": "123"},
    {"username": "zero", "passwd": "123"},
    {"username": "abc", "passwd": "123"}
]

login_dict = {"username": None, "login": False}    # 這裡用這個字典模擬session的功能

def login_func(func):
    def wrapper(*args, **kwargs):
        if login_dict["username"] and login_dict["login"]:
            ret = func(*args, **kwargs)
            return ret
        while True:
            username = input("請輸入用戶名:").strip()
            passwd = input("請輸入密碼:").strip()
            for dict in user:
                if dict["username"] == username and dict["passwd"] == passwd:
                    login_dict["username"] = username
                    login_dict["login"] = True
                    ret = func(*args, **kwargs)
                    return ret
            else:
                print("用戶名或密碼錯誤!")
                time.sleep(1)
                print("請重新輸入----->")
    return wrapper

@login_func
def index():
    print("歡迎來到zero的網站主頁!")

@login_func
def home(name):
    print("【%s】,歡迎來到個人中心!" % (name))

@login_func
def shopping(name):
    print("【%s】,現在您的購物車中有 %s , %s , %s " % (name, "牛奶", "香蕉", "芒果"))

index()
home("python工程師")
shopping("python工程師")

3.4 帶參數的裝飾器

import time
user = [
    {"username": "hgzero", "passwd": "123"},
    {"username": "wzh", "passwd": "123"},
    {"username": "hg", "passwd": "123"},
    {"username": "zero", "passwd": "123"},
    {"username": "abc", "passwd": "123"}
]

login_dict = {"username": None, "login": False}    # 這裡用這個字典模擬session的功能

def func(db_type):
    def login_func(func):
        print("這個資料庫的類型是:", db_type)
        def wrapper(*args, **kwargs):
            if login_dict["username"] and login_dict["login"]:
                ret = func(*args, **kwargs)
                return ret
            while True:
                username = input("請輸入用戶名:").strip()
                passwd = input("請輸入密碼:").strip()
                for dict in user:
                    if dict["username"] == username and dict["passwd"] == passwd:
                        login_dict["username"] = username
                        login_dict["login"] = True
                        ret = func(*args, **kwargs)
                        return ret
                else:
                    print("用戶名或密碼錯誤!")
                    time.sleep(1)
                    print("請重新輸入----->")
        return wrapper
    return login_func


@func("MySQL")  #  這裡先執行func("MySQL")  --->   index = login_func(index)
# 這裡因為後面加了括弧,所以是執行了func函數,執行了func函數所得到的返回值就是login_func函數的地址
# func()函數執行得到的地址就是login_func,所以這裡就等價於是 @login_func,同時也將參數傳進去了,巧妙的運用了閉包的原理
def index():
    print("歡迎來到zero的網站主頁!")

@func("Mongodb")
def home(name):
    print("【%s】,歡迎來到個人中心!" % (name))

@func("Redis")
def shopping(name):
    print("【%s】,現在您的購物車中有 %s , %s , %s " % (name, "牛奶", "香蕉", "芒果"))

index()
home("python工程師")
shopping("python工程師")