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

Praywu發表於2020-12-10

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工程師")

 

 

 

 

 

 

 

相關文章