主頁 > 後端開發 > Python迭代器、生成器和修飾器-你會用yield嗎?

Python迭代器、生成器和修飾器-你會用yield嗎?

2021-02-25 11:41:00 後端開發

文章目錄

  • 迭代器
    • 概念
  • 生成器
    • 概念
    • yield語法
    • 用途
  • 修飾器
    • 修飾器模式
    • Python修飾器
    • 定義
    • 應用

在這里插入圖片描述
yield 英 [ji?ld] 美 [ji?ld]
v.出產(作物);產生(收益、效益等);提供;屈服;讓步;放棄;繳出
n.產量;產出;利潤
上面路牌是「讓」的意思
在這里插入圖片描述

迭代器

概念


迭代器是什么?學習過C/C++的朋友可能熟悉,是在STL中用來對特定的容器進行訪問,能夠遍歷STL容器中的元素,

迭代器提供一些基本運算子:*、++、==|!+、=等,這些操作和C/C++操作array元素時的指標介面基本一致,不同的是迭代器具有遍歷復雜資料結構的能力,即所謂的智能指標,

比如對串列和元組做for...in遍歷操作時,Python實際上時通過串列和元組的迭代物件來實作的,而不是串列和元組本身:
在這里插入圖片描述

Python中,迭代器還擁有迭代用戶自定義類的能力,迭代器物件需要支持__iter__()next()兩個方法,前者回傳迭代器本身,后者回傳下一個元素,

迭代自定義類舉例:

class example(object):
    def __init__(self,num):
        self.num=num
    def __iter__(self):
        return self
    def __next__(self):
        if self.num <= 0:
            raise StopIteration
        tmp = self.num
        self.num -= 1
        return tmp
    
a = example(3)
print(a.__next__())
print(a.__next__())
print(a.__next__())
print(a.__next__())

類example是一個迭代物件,每次執行next()操作時會判斷self.num屬性,如果<=0則拋出例外表示迭代結束,
在這里插入圖片描述

生成器

概念


當類線性遍歷操作時,可以通過迭代器的__iter()____next__()方法來實作,但是不夠靈活方便,比如對函式來說沒有屬性變數來存放狀態,即不支持函式的線性遍歷,

那這怎么解決?Python3.X支持使用yield生成器的方法來進行線性遍歷,yield陳述句僅用于定義生成器函式,且只能出現在生成器函式內,當生成器函式被呼叫時回傳一個生成器,

那生成器又是什么?生成器的概念源于協同作業的程式,比如消費者和生產者模型,Python生成器就是其中生產者的角色(資料提供者),每次生成器程式就等在那里,一旦消費者/用戶呼叫next()方法,生成就繼續執行下一步,然后把當前遇到的內部資料的Node放到下一個消費者用戶能夠看到的公用緩沖區里,然后停下來等待wait,最后消費者/用戶從緩沖區里獲得Node

例如實作一個遞增的生成器:

def add():
    num = 0
    while True:
        yield num
        num += 1

a = add()
print(next(a))
print(next(a))
print(next(a))

定義生成器函式add(),只有在用戶呼叫next()方法時,才將內部資料的Node提供出來然后等待,而不是陷入無限回圈,
在這里插入圖片描述
從上看出yield運算式的功能可以分成兩點:

  1. 回傳資料num
  2. 進入wait_and_get狀態,可以理解為程式在這個位置暫停,當消費者再次呼叫`next()方法時,程式才會這個位置激活,

迭代器VS生成器
都是用戶通過next()方法來獲取資料,不過迭代器是通過自己實作的next()方法來逐步回傳資料,而生成器是使用yield自動提供資料并讓程式暫停wait狀態,等待用戶進一步操作,即生成器更加靈活方便,

yield語法


一、yield是運算式
在Python3.X中,yield成為運算式,不再是陳述句,但是必須放在函式內部,如果寫成陳述句的形式會報錯(實際上回傳值被扔掉了),例如:

yield n
x=yield n

既然yield是運算式,所以可以和其他運算式組合使用,例如:

x=y+z*(yield 2)
a=b+c+yield d

二、next()方法
當用戶呼叫next()方法執行到yield運算式時,先回傳n,然后程式進入wait狀態,只有當下一次執行next()方法時才會從此處恢復并繼續執行下面的代碼,一直執行到下一個yield運算式,如果沒有下一個yield代碼,就拋出StopIteration例外,
在這里插入圖片描述

三、send(msg)方法
執行一個send(msg)會恢復生成器的運行,然后發送的值msg將成為當前yield運算式的回傳值,程式恢復運行之后,會繼續執行下面的代碼,也是一直執行到下一個yield代碼,如果沒有下一個則拋出StopIteration例外,
當使用send(msg)發送訊息給生成器時,wait_and_get會檢測到這個新型,然后喚醒生成器,同時該方法獲取msg并復制給x,如下代碼所示:
在這里插入圖片描述
上述函式等價于:

def test():
    print('記得一鍵三連')
    put(1)
    x = wait_and_get()
    print('x=', x)
    put(2)
    y = wait_and_get()
    print('y=', y)
    print('下面無yield了,會拋Stop例外')

當第一次呼叫next()方法執行到第一個wait_and_get處時生成器進入wait狀態并列印‘記得一鍵三連’和’1’;
接著呼叫send(666),從第一個wait_and_get處啟動生成器,并把引數666賦值給變數x,然后繼續執行到第二個wait_and_get處,生成器又進入wait狀態;
接著呼叫send(888),生成器從第二個wait_and_get處啟動,并把引數888賦值給變數y,后面因為沒有yield運算式了,所以生成器拋出StopIteration例外,

特別注意的是,第一個呼叫要么使用next(),要么使用send(None),不能使用send()來發送一個非None值,原因是非None值是發給wait_and_get的,一開始程式并沒有停在wait_and_get代碼處,只有先使用next()或者send(None)方法后才會停止wait_and_get處,這時才能使用send發送一個非None值,
在這里插入圖片描述
在這里插入圖片描述

四、throw()方法
生成器提供throw()方法從生成器內部來引發例外,從而控制生成器的執行,
在這里插入圖片描述
GeneratorExit的作用是讓生成器有機會執行一些退出時的清理作業,

五、關閉生成器
生成器提供了一個close()方法來關閉生成器,當使用close()方法時,生成器會直接從當前狀態退出,再使用next()時會得到StopIteration例外,
在這里插入圖片描述
實際上,close()方法也是通過throw()方法來發送GenerationExit例外來關閉生成器的,其實作相當于如下代碼:

def close(self):
    try:
        self.throw(GeneratorExit)
    except(GeneratorExit,StopIteration):
        pass
    else:
        raise RuntimeError("generator ignored GeneratorExit")

插播反爬資訊 )博主CSDN地址:https://wzlodq.blog.csdn.net/

用途


一、節省記憶體
相對于串列、元組,生成器更節省記憶體,生成器一次產生一個資料項,直到沒有為止,在for回圈中可以對它進行回圈處理,占用記憶體更少,但是需要記住當前的狀態,以便回傳下一個資料項,生成器是一個有next()方法的物件,序列型別則保存了所有的資料項,通過索引來進行訪問,

比如求閏年:
在這里插入圖片描述
在這里插入圖片描述

二、線性遍歷
生成器可以將非線性話的處理轉換為線性的方式,典型的例子就是對二叉樹的訪問,
傳統做法是用遞回:

def deal_tree(node):
    if not node:
        return
    if node.leftnode:
        deal_tree(node.leftnode)
    process(node)
    if node.rightnode:
        deal_tree(node.rightnode)

遞回做法中為了處理每一個節點,需要將處理方法process()放到訪問的程序中,極容易出錯,也不清晰,比較好的方法是先將樹的節點訪問轉換成線性,用生成器實作:

def deal_tree(node):
    if not node:
        return
    if node.leftnode:
        for i in walk_tree(node.leftnode):
            yield i
    yield node
    if node.rightnode:
        for i in walk_tree(node.rightnode):
            yield i

修飾器

修飾器模式


修飾器是用于擴展原來函式功能的一種函式,這個函式的特殊之處在于回傳值是一個函式,

修飾器(Decorator)的概念來自于設計模式,也叫裝飾者模式,具體細節可見設計模式系列博客,舉個例子,如游戲英雄的戰力提升,可以通過升級裝備、加技能、使用道具等等,實作時自然不可能把每一種組合的情況都創建出來以備呼叫,修飾器則發揮作用了:升級裝備時使用升級裝備的修飾器;使用道具時用道具的修飾器,目的是為了運行時動態的改變物件的狀態而不是編譯期,使用組合的方式來增減Decorator而不是修改原有的代碼來滿足業務的需要,以利于程式的擴展,

修飾器模式是針對Java語言的,為了靈活使用組合的方式來增減Decorator,Java語言需要使用較為復雜的類物件結構才能達到效果,Python從語法層次上實作了使用組合的方式來增減Decorator的功能,

Python修飾器


Python從語法層次上支持Decorator模式的靈活呼叫,主要有以下兩種方式:
一、不帶引數的Decorator
語法形式如下:

@A
def f():

相當于在函式f上加一個修飾器A,Python會處理為f = A(f)
修飾器的添加時不受限制的,可以多層次使用:

@A
@B
@C
def f():

Python會處理為f=A(B(C(f)))

二、帶引數的Decorator
語法形式如下:

@A(args)
def f():

Python會先執行A(args)得到一個decorator()函式,處理為:

def f():...
_deco = A(args)
f = _deco(f)

定義


每一個Decorator都對應有相應的函式,要對后面的函式進行處理,要么回傳原來的函式物件,要么回傳一個新的函式物件,特別注意Decorator只能用來處理函式和類方法,

根據上述修飾器兩種呼叫方法,修飾器函式定義也對應兩種方法:
一、不帶引數
對func處理后回傳原函式物件,語法形式如下:

def A(func):
	#處理func
	return func
	
@A
def f(args):pass

回傳一個新的函式物件,注意neew_func的定義形式要與待處理函式相同(可以用不定引數),語法形式如下:

def A(func):
	def new_func(*args,**argkw):
		#做一些額外作業
		return func(*args,**argkw) #呼叫原函式繼續進行處理
	return new_func
	
@A
def f(args):pass

上述代碼在A中定義了新的函式,然后A回傳這個新函式,在新函式中可以先處理一些事情再呼叫原始函式進行處理,如果想在呼叫函式之后再進一步處理,可以通過函式回傳值來實作:

def A(args):
	def new_func(*args,**argkw):
		#一些呼叫前處理作業
		result = func(*args,**argkw)
		if result:
			#進一步呼叫后作業
			return new_result
		else:
			return result
	return new_func

@A
def f(args):pass

二、帶引數
因為decorator()函式使用了引數進行呼叫,所以要回傳一個新的decorator()函式,這樣就與第一種形式一致了,

def A(arg):
	def _A(func):
		def new_func(args):
			#做一些作業
			return func(args)
		return new_func
	return _A

@A(arg)
def f(args):pass

應用


使用Decorator可以增加程式的靈活性,減少耦合度,適合用于用戶登錄檢查、日志處理等,這種通過函式之間相互結合的方式更符合搭積木的要求,可以把函式功能進一步分解,使得功能足夠簡單單一,然后再通過Decorator的機制靈活把函式串起來,

應用舉例:一個多用戶使用的程式會有很多功能和權限相關,傳統方法是建立權限角色類,然后每個用戶繼承權限角色,但這種方法不但容易出錯,而且對管理、修改也很麻煩,采用Decorator來處理,通過decorator()函式的呼叫來處理用戶權限的邏輯,可以先定義權限管理的類:

class Permission:
    #普通用戶
    def userPermission(self,function):
        def new_func(*args,**kwargs):
            obj = args[0]
            if obj.user.hasUserPermission(): #判斷擁有對應權限
                ret = function(*args,**kwargs)
            else:
                ret = 'No User Permission'
            return ret
        return new_func
    #管理員
    def adminPermission(self,function):
        def new_func(*args,**kwargs):
            obj = args[0]
            if obj.user.hasAdminpermission(): #判斷擁有對應權限
                ret = function(*args,**kwargs)
            else:
                ret ='No Admin Permission'
            return ret
        return new_func

然后處理實際業務代碼是,只要為需要的功能加上實際權限限制Decorator就可以了:

class Action:
    def __init__(self,name):
        self.user = UserService.newUser(name)
    @Permission.userPermission #需要用戶權限
    def listAllpoints(self):
        return 'do real list all points'
    @Permission.adminPermission #需要管理員權限
    def setup(self):
        return 'do real setup'

最后就是業務方法的呼叫了:

if __name__ == "main":
    action = Action('user')
    print(action.listAllpoints())#執行真正的業務代碼
    print(action.setup)

相對于傳統方法,Decorator使用起來優勢很大,可以將用戶權限檢查這些瑣碎的作業和業務呼叫代碼相剝離,并且能夠檢測函式方便地修飾到業務邏輯代碼之上,
Python系列博客持續更新中

原創不易,請勿轉載本不富裕的訪問量雪上加霜
博主首頁:https://wzlodq.blog.csdn.net/
微信公眾號:唔仄lo咚鏘
如果文章對你有幫助,記得一鍵三連?

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/263457.html

標籤:python

上一篇:資料科學庫之——matplotlib

下一篇:SpringBoot的配置【組態檔、加載順序、配置原理】(超詳細)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more