Python之裝飾器、迭代器和生成器

来源:http://www.cnblogs.com/liubinsh/archive/2017/07/25/7236812.html
-Advertisement-
Play Games

在學習python的時候,三大“名器”對沒有其他語言編程經驗的人來說,應該算是一個小難點,本次博客就博主自己對裝飾器、迭代器和生成器理解進行解釋。 為什麼要使用裝飾器 什麼是裝飾器?“裝飾”從字面意思來誰就是對特定的建築物內按照一定的思路和風格進行美化的一種行為,所謂“器”就是工具,對於python ...


在學習python的時候,三大“名器”對沒有其他語言編程經驗的人來說,應該算是一個小難點,本次博客就博主自己對裝飾器、迭代器和生成器理解進行解釋。

為什麼要使用裝飾器

       什麼是裝飾器?“裝飾”從字面意思來誰就是對特定的建築物內按照一定的思路和風格進行美化的一種行為,所謂“器”就是工具,對於python來說裝飾器就是能夠在不修改原始的代碼情況下給其添加新的功能,比如一款軟體上線之後,我們需要在不修改源代碼和不修改被調用的方式的情況下還能為期添加新的功能,在python種就可以用裝飾器來實現,同樣在寫代碼的時候也要考慮到後面的可擴展性,下麵我們來看一步一步的看一下python的裝飾器。

 

一個簡單例子引入無參裝飾器

先來看簡單的幾行代碼,代碼的運行結果是先睡2秒,再列印"hello boy!":

import time
def  foo():
    """列印"""
    time.sleep(2)
    print("Hello boy!")
foo()

我們現在我們需要為其添加一個程式計時功能,但是不能修改原始的代碼:

import time
def timmer(func):
    def wrapper():
        """計時功能"""
        time_start=time.time()
        func()
        time_end=time.time()
        print("Run time is %f "%(time_end-time_start))
    return wrapper
def  foo():
    """列印"""
    time.sleep(2)
    print("Hello boy!")
foo=timmer(foo)
foo()
#運行結果
Hello boy!
Run time is 2.000446 

看!我們沒有修改原來的代碼就實現了這個功能,因為函數也是對象,所以能夠將函數foo當做參數傳遞給了函數timmer。

在python中,有個更簡潔的方式來取代foo=timmer(foo),使用@timmer這種方式,這個在python中被稱為語法糖

import time
def timmer(func):
    def wrapper():
        """計時功能"""
        time_start=time.time()
        func()
        time_end=time.time()
        print("Run time is %f "%(time_end-time_start))
    return wrapper
@timmer      #等於  foo=timmer(foo)
def  foo():
    """列印"""
    time.sleep(2)
    print("Hello boy!")
foo()

下麵我們來一步一步的分析函數的執行過程:

1.導入time模塊

import time

2.定義函數timmer,定義函數並不會執行函數內的代碼

def timmer(func):

3.調用裝飾器,相當於foo=timer(foo),就是把函數foo作為參數穿給了函數timmer

@timmer

4.運行函數timmer,接受了參數   func=foo

def timmer(func):

5.在函數timmer內,定義了函數wrapper,wrapper函數內部代碼也不執行,然後將函數wrapper作為返回值返回

return wrapper

6.將返回值賦值給了foo,在第3步中,foo=timmer(foo),還記吧

@timmer      #等於  foo=timmer(foo)

7.運行函數foo(),但是這裡的函數已經不是原來的那個函數了,可以列印foo,對的,因為之前我們將wrapper作為返回值傳給了foo,所以在這裡執行foo就是在執行wrapper了,為了再確定這一點你也可列印wrapper,它們的記憶體地址相同,所以都是指向同一個地址空間:

<function timmer.<locals>.wrapper at 0x00000180E0A8A950>   #列印foo的結果
<function timmer.<locals>.wrapper at 0x000001F10AD8A950>   #列印wrapper的結果
foo()

8.運行函數wrapper,記錄開始時間,執行函數func,在第4步的時候,func被foo賦值,運行func就是在運行原函數foo,睡2秒,列印字元串;

time_start=time.time()
    time.sleep(2)
    print("Hello boy!")

9.記錄結束時間,列印運行時間,程式結束。

Hello boy!
Run time is 2.000161 

 

有參裝飾器

 在前面的例子中,原函數沒有參數,下麵的來看一個當原函數有參數,該怎麼修改裝飾器函數呢?

import time
def timmer(func):
    def wrapper(*args,**kwargs):
        """計時功能"""
        start_time=time.time()
        res=func(*args,**kwargs)
        end_time=time.time()
        print("Run time is %f"%(end_time-start_time))
        return res
    return wrapper
@timmer    
def  my_max(x,y):
    """返回兩個值的最大值"""
    res=x if x > y else y
    time.sleep(2)
    return res
res=my_max(1,2)
print(res)
#運行結果
Run time is 2.000175
2

 當原函數有需要傳入參數的時候,在這個例子my_max有兩個位置形成需要傳入參數,只需要在wrapper上添加兩個形參,本例子中使用了可變參數(*args,**kwargs)也是可以的,這是@timmer就等於my_max(1,2)=timmer(my_max)

 下麵我們來看一個有參數的裝飾器:

def auth(filetype):
    def auth2(func):
        def wrapper(*args,**kwargs):
            if filetype == "file":
                username=input("Please input your username:")
                passwd=input("Please input your password:")
                if passwd == '123456' and username == 'Frank':
                    print("Login successful")
                    func()
                else:
                    print("login error!")
            if filetype == 'SQL':
                print("No SQL")
        return wrapper
    return auth2
@auth(filetype='file')  #先先返回一個auth2 ==》@auth2  ==》 index=auth2(index) ==》 index=wrapper
def index():
    print("Welcome to China")
index()

如果裝飾器本身有參數,就需要多一層內嵌函數,下麵我們一步一步分析執行流程:

1.定義函數auth

def auth(filetype):

 2.調用解釋器,首先要運行函數auth(filetype='file')

@auth(filetype='file')

 3.運行函數auth,定義了一個函數auth2,並作為返回值返回,那麼這個@auth(filetype='file')就等同於@auth2,等同於index=auth2(index)

def auth(filetype):
    def auth2(func):
        def wrapper(*args,**kwargs):
        return wrapper
    return auth2

 4.auth2(index)執行,func=index,定義函數wrapper,並返回之,這時候index其實就是等於wrapper了

def wrapper(*args,**kwargs):
return wrapper

 5.當運行index,即運行wrapper,運行函數內部代碼,filetype=="file",提示用戶輸出用戶名和密碼,判斷輸入是否正確,如果正確,則執行函數func(),等於執行原來的index,列印

if filetype == "file":
                username=input("Please input your username:")
                passwd=input("Please input your password:")
                if passwd == '123456' and username == 'Frank':
                    print("Login successful")
                    func()

 6.運行結果測試

Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China

 裝飾器也是可以被疊加的:

import time
#
def timmer(func):
    def wrapper():
        """計時功能"""
        time_start=time.time()
        func()
        time_end=time.time()
        print("Run time is %f "%(time_end-time_start))
        # print("---",wrapper)
    return wrapper
def auth(filetype):
    def auth2(func):
        def wrapper(*args,**kwargs):
            if filetype == "file":
                username=input("Please input your username:")
                passwd=input("Please input your password:")
                if passwd == '123456' and username == 'Frank':
                    print("Login successful")
                    func()
                else:
                    print("login error!")
            if filetype == 'SQL':
                print("No SQL")
        return wrapper
    return auth2
@timmer
@auth(filetype='file')  #先先返回一個auth2 ==》@auth2  ==》 index=auth2() ==》 index=wrapper
def index():
    print("Welcome to China")
index()

#測試結果
Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China
Run time is 7.966267 

 註釋優化

import time
def timmer(func):
    def wrapper():
        """計算程式運行時間"""
        start_time=time.time()
        func()
        end_time=time.time()
        print("Run time is %s:"%(end_time-start_time))
    return wrapper
@timmer
def my_index():
    """列印歡迎"""
    time.sleep(1)
    print("Welcome to China!")
my_index()
print(my_index.__doc__)

#運行結果
Welcome to China!
Run time is 1.0005640983581543:
計算程式運行時間
View Code

當我們使用了裝飾器的時候,雖然沒有修改代碼本身,但是在運行的時候,比如上面這個例子,運行my_index其實在運行wrapper了,如果我們列印my_index的註釋信息,會列印wrapper()的註釋信息,那麼該怎麼優化?

可以在模塊functools中導入wraps,具體見以下:

import time
from functools import wraps
def timmer(func):
    @wraps(func)
    def wrapper():
        """計算程式運行時間"""
        start_time=time.time()
        func()
        end_time=time.time()
        print("Run time is %s:"%(end_time-start_time))
    return wrapper
@timmer
def my_index():
    """列印歡迎"""
    time.sleep(1)
    print("Welcome to China!")
my_index()
print(my_index.__doc__)
#運行結果
Welcome to China!
Run time is 1.0003223419189453:
列印歡迎

 這樣,在錶面看來,原函數沒有發生任何變化。

 

 為什麼要用迭代器

從字面意思,迭代就是重覆反饋過程的活動,其目的通常是為了比較所需目標或結果,在python中可以用迭代器來實現,先來描述一下迭代器的優缺點,如果看不懂可以先略過,等看完本博客再回頭看,相信你會理解其中的意思:

優點:

迭代器在取值的時候是不依賴於索引的,這樣就可以遍歷那些沒有索引的對象,比如字典和文件

迭代器與列表相比,迭代器是惰性計算,更節省記憶體

缺點:

無法獲取迭代器的長度,沒有列表靈活

只能往後取值,不能倒著取值

 

 什麼是迭代器

那麼在python什麼才算是迭代器呢?

只要對象有__iter__(),那麼它就是可迭代的,迭代器可以使用函數next()來取值

下麵我們來看一個簡單的迭代器:

my_list=[1,2,3]
li=iter(my_list)      #li=my_list.__iter__()
print(li)
print(next(li))
print(next(li))
print(next(li))
#運行結果
<list_iterator object at 0x000002591652C470>
1
2
3

 可以看到,使用內置函數iter可以將列表轉換成一個列表迭代器,使用next()獲取值,一次值取一個值,當值取完了,再使用一次next()的時候,會報異常StopIteration,可以通過異常處理的方式來避免,try-except-else就是一個最常用的異常處理結構:

my_list=[1,2,3]
li=iter(my_list)
while True:
    try:
        print(next(li))
    except StopIteration:
        print("Over")
        break
    else:
        print("get!")
#運行結果
1
get!
2
get!
3
get!
Over

 我們學過的for迴圈其實就是在對象後面加上了方法__iter__(),使對象成為了一個迭代器,然後再一一遍歷其中的值,使用迭代器一次一次的next(),每次在記憶體中只會占一個值的空間。

 

 查看可迭代對象和迭代器對象

 使用Iterable模塊可以判斷對象是否是可迭代的:

from collections import Iterable
s="hello"   #定義字元串
l=[1,2,3,4]  #定義列表
t=(1,2,3)    #定義元組
d={'a':1}    #定義字典
set1={1,2,3,4}  #定義集合
f=open("a.txt")  #定義文本
# 查看是否都是可迭代的
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
#運行結果
True
True
True
True
True
True

通過判斷,可以確定我們所知道的常用的數據類型都是可以被迭代的。

使用Iterator模塊可以判斷對象是否是迭代器:

from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("a.txt")
# 查看是否都是可迭代的
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
#運行結果
False
False
False
False
False
True

 可知只有文件是迭代器,所以可以直接使用next(),而不需要轉換成迭代器。

 

 什麼是生成器

生產器就是一個是帶有yield的函數

下麵來看一個簡單的生成器

def my_yield():
    print('first')
    yield 1
g=my_yield()
print(g)
#運行結果
<generator object my_yield at 0x0000024366D7E258>

 生成器也是一個迭代器

from collections import Iterator
def my_yield():
    print('first')
    yield 1
g=my_yield()
print(isinstance(g,Iterator))
#運行結果
True

 那就可以用next()來取值了

print(next(g))
#運行結果
first
1

 

 

 生成器的執行過程

 我們來看以下下麵這個例子,瞭解生產的執行流程

def my_yield():
    print('first')
    yield 1
    print('second')
    yield 2
    print('Third')
    yield 3
g=my_yield()
next(g)
next(g)
next(g)
#運行結果
first
second
Third

 1.定義生成器my_yield,並將其賦值給了g

def my_yield():
g=my_yield()

 2.開始第一次執行next(),開始執行生產器函數 ,列印第一語句,遇到yileld的時候暫停,並返回一個1,如果你想列印返回值的話,這裡會顯示1

    print('first')
    yield 1

 3.再執行2次,列印字元串(每執行一次都會暫停一下)

    print('second')
    yield 2
    print('Third')
    yield 3

4.如果再加一次next()就會報出StopIteration異常了

生成器在每次暫停的時候,函數的狀態將被保存下來,來看下麵的例子:

def foo():
    i=0
    while  True:
        yield i
        i+=1
g=foo()
for num in g:
    if num < 10:
        print(num)
    else:
        break
#運行結果
0
1
2
3
4
5
6
7
8
9

 for迴圈中隱含next(),每next一次,暫停一次,if語句判斷一次,然後執行下一次next,可以看到我們的while迴圈並沒有無限迴圈下去,而是狀態被保存下來了。

 

 協程函數

 我們來看下麵這個生成器和執行結果

def eater(name):
    print('%s start to eat food'%name)
    while True:
        food=yield
        print('%s get %s ,to start eat'%(name,food))
    print('done')
e=eater('Frank')
next(e)
e.send('egg')  #給yield送一個值,並繼續執行代碼
e.send('tomato')
#運行結果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat

send可直接以向yield傳值,含有yield表達式的函數我們也稱為協程函數,

這運行程式的時候,不可以直接send,必須先使用next()初始化生成器。

如果存在多個這樣的函數,那麼我們每次執行的時候都要去next()一下,為了防止忘記這一步操作,可以使用裝飾器初始化:

def init(func):
    def wrapper(*args):
        res = func(*args)
        next(res)     #  在這裡執行next
        return res
    return wrapper
@init
def eater(name):
    print('%s start to eat food'%name)
    while True:
        food=yield
        print('%s get %s ,to start eat'%(name,food))
    print('done')
e=eater('Frank')
e.send('egg') 
e.send('tomato')

 所以在程式中有更多的生成器需要初始化的時候,直接調用這個裝飾器就可以了。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡提供在使用python進行開發中常使用到的方法技巧,如有不對歡迎批評指正。 要點:開發中類、變數特性查詢,類型就是類,斷言的使用,深淺複製判斷等 python腳本文件是使用UTF-8編碼的,所以在發現中文字元出現亂碼時應當考慮是否文本文件採用UTF-8編碼。 如果想指定不同的編碼需要在源碼文件中 ...
  • package cn.itheima.store.product.domain;import java.io.Serializable;import java.util.List;public class PageBean<T> implements Serializable{ private in ...
  • 題目:定義一個函數,輸入一個鏈表的頭結點,反轉該鏈表並輸出反轉後的鏈表的頭結點。解題思路:單向鏈表只能實現單向遍歷,改變鏈表方向就是要把當前鏈表的節點指向它的前一個節點,一旦當前鏈表指向發生了變化,就不能根據此節點獲取到它後面的節點,所以在改變方向前要保存當前節點的下一節點,防止鏈表斷開,因此需要三 ...
  • N個人坐成一個圓環(編號為1 - N),從第1個人開始報數,數到K的人出列,後面的人重新從1開始報數。問最後剩下的人的編號。 例如:N = 3,K = 2。2號先出列,然後是1號,最後剩下的是3號。 N個人坐成一個圓環(編號為1 - N),從第1個人開始報數,數到K的人出列,後面的人重新從1開始報數 ...
  • MPI Maelstrom MPI Maelstrom 總時間限制: 1000ms 記憶體限制: 65536kB描述BIT has recently taken delivery of their new supercomputer, a 32 processor Apollo Odyssey dis ...
  • 博客園地址:天清如願的博客。 ...
  • 轉載:http://blog.csdn.net/inter_peng/article/details/41021727 1. Action/Service/DAO簡介: Action是管理業務(Service)調度和管理跳轉的。 Service是管理具體的功能的。 Action只負責管理,而Serv ...
  • 浮點數是電腦中儲存實數的形式。我們時常需要用浮點數去處理帶小數點的運算。可你是否知道,浮點數還有這些操作: 正負無窮大 與整數不同,浮點數沒有溢出的概念。當浮點數的運算結果超過一定範圍時,它的值就會根據運算結果的符號變成正無窮大或負無窮大。最簡單產生無窮大的運算就是除以0.例如1.0/0.0的結果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...