網站首頁 編程語言 正文
1.迭代器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會后退。
1.1 可迭代對象
我們已經知道可以對list、tuple、str等類型的數據使用for...in...的循環語法從其中依次拿到數據進行使用,我們把這樣的過程稱為遍歷,也叫迭代。
但是,是否所有的數據類型都可以放到for...in...的語句中,然后讓for...in...每次從中取出一條數據供我們使用,即供我們迭代嗎?
>>> for i in 100: ... print(i) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not iterable >>> # int整型不是iterable,即int整型不是可以迭代的
我們把可以通過for...in...這類語句迭代讀取一條數據供我們使用的對象稱之為可迭代對象(Iterable)。
1.2 如何判斷一個對象是否可以迭代
可以使用 isinstance() 判斷一個對象是否是 Iterable 對象:
In [50]: from collections import Iterable In [51]: isinstance([], Iterable) Out[51]: True In [52]: isinstance({}, Iterable) Out[52]: True In [53]: isinstance('abc', Iterable) Out[53]: True In [54]: isinstance(mylist, Iterable) Out[54]: False In [55]: isinstance(100, Iterable) Out[55]: False
1.3 可迭代對象的本質
我們分析對可迭代對象進行迭代使用的過程,發現每迭代一次(即在for...in...中每循環一次)都會返回對象中的下一條數據,一直向后讀取數據直到迭代了所有數據后結束。那么,在這個過程中就應該有一個“人”去記錄每次訪問到了第幾條數據,以便每次迭代都可以返回下一條數據。我們把這個能幫助我們進行數據迭代的“人”稱為迭代器(Iterator)。
可迭代對象的本質就是可以向我們提供一個這樣的中間“人”即迭代器幫助我們對其進行迭代遍歷使用。
可迭代對象通過__iter__
方法向我們提供一個迭代器,我們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,然后通過這個迭代器來依次獲取對象中的每一個數據.
那么也就是說,一個具備了__iter__
方法的對象,就是一個可迭代對象。
from collections.abc import Iterable class Demo(object): def __init__(self, n): self.n = n self.current = 0 def __iter__(self): pass demo = Demo(10) print(isinstance(demo, Iterable)) # True for d in demo: # 重寫了 __iter__ 方法以后,demo就是一個一個可迭代對象了,可以放在for...in的后面 print(d) # 此時再使用for...in循環遍歷,會提示 TypeError: iter() returned non-iterator of type 'NoneType' # 這是因為,一個可迭代對象如果想要被for...in循環,它必須要有一個迭代器
1.4 迭代器Iterator
通過上面的分析,我們已經知道,迭代器是用來幫助我們記錄每次迭代訪問到的位置,當我們對迭代器使用next()函數的時候,迭代器會向我們返回它所記錄位置的下一個位置的數據。實際上,在使用next()函數的時候,調用的就是迭代器對象的__next__
方法(Python3中是對象的__next__
方法,Python2中是對象的next()方法)。所以,我們要想構造一個迭代器,就要實現它的*next*方法。但這還不夠,python要求迭代器本身也是可迭代的,所以我們還要為迭代器實現__iter__
方法,而__iter__
方法要返回一個迭代器,迭代器自身正是一個迭代器,所以迭代器的__iter__
方法返回自身即可。
一個實現了*iter*方法和*next*方法的對象,就是迭代器。
class MyIterator(object): def __init__(self, n): self.n = n self.current = 0 # 自定義迭代器需要重寫__iter__和__next__方法 def __iter__(self): return self def __next__(self): if self.current < self.n: value = self.current self.current += 1 return value else: raise StopIteration my_it = MyIterator(10) for i in my_it: # 迭代器重寫了__iter__方法,它本身也是一個可迭代對象 print(i)
1.5 如何判斷一個對象是否迭代器
調用一個對象的__iter__
方法,或者調用iter()內置函數,可以獲取到一個可迭代對象的迭代器。
names = ['hello', 'good', 'yes'] print(names.__iter__()) # 調用對象的__iter__()方法 print(iter(names)) # 調用iter()內置函數
可以使用 isinstance() 判斷一個對象是否是 Iterator 對象:
from collections.abc import Iterator names = ['hello', 'good', 'yes'] print(isinstance(iter(names), Iterator))
1.6 for...in...循環的本質
for item in Iterable 循環的本質就是先通過iter()函數獲取可迭代對象Iterable的迭代器,然后對獲取到的迭代器不斷調用next()方法來獲取下一個值并將其賦值給item,當遇到StopIteration的異常后循環結束。
1.7 迭代器的應用場景
我們發現迭代器最核心的功能就是可以通過next()函數的調用來返回下一個數據值。如果每次返回的數據值不是在一個已有的數據集合中讀取的,而是通過程序按照一定的規律計算生成的,那么也就意味著可以不用再依賴一個已有的數據集合,也就是說不用再將所有要迭代的數據都一次性緩存下來供后續依次讀取,這樣可以節省大量的存儲(內存)空間。
舉個例子,比如,數學中有個著名的斐波拉契數列(Fibonacci),數列中第一個數為0,第二個數為1,其后的每一個數都可由前兩個數相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
現在我們想要通過for...in...循環來遍歷迭代斐波那契數列中的前n個數。那么這個斐波那契數列我們就可以用迭代器來實現,每次迭代都通過數學計算來生成下一個數。
class FibIterator(object): """斐波那契數列迭代器""" def __init__(self, n): """ :param n: int, 指明生成數列的前n個數 """ self.n = n # current用來保存當前生成到數列中的第幾個數了 self.current = 0 # num1用來保存前前一個數,初始值為數列中的第一個數0 self.num1 = 0 # num2用來保存前一個數,初始值為數列中的第二個數1 self.num2 = 1 def __next__(self): """被next()函數調用來獲取下一個數""" if self.current < self.n: num = self.num1 self.num1, self.num2 = self.num2, self.num1+self.num2 self.current += 1 return num else: raise StopIteration def __iter__(self): """迭代器的__iter__返回自身即可""" return self if __name__ == '__main__': fib = FibIterator(10) for num in fib: print(num, end=" ")
2.生成器
利用迭代器,我們可以在每次迭代獲取數據(通過next()方法)時按照特定的規律進行生成。但是我們在實現一個迭代器時,關于當前迭代到的狀態需要我們自己記錄,進而才能根據當前狀態生成下一個數據。為了達到記錄當前狀態,并配合next()函數進行迭代使用,我們可以采用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器。
2.1 創建生成器方法1
要創建一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改成 ( )
In [15]: L = [ x*2 for x in range(5)] In [16]: L Out[16]: [0, 2, 4, 6, 8] In [17]: G = ( x*2 for x in range(5)) In [18]: G Out[18]: <generator object <genexpr> at 0x7f626c132db0> In [19]:
創建 L 和 G 的區別僅在于最外層的 [ ] 和 ( ) , L 是一個列表,而 G 是一個生成器。我們可以直接打印出列表L的每一個元素,而對于生成器G,我們可以按照迭代器的使用方法來使用,即可以通過next()函數、for循環、list()等方法使用。
In [19]: next(G) Out[19]: 0 In [20]: next(G) Out[20]: 2 In [21]: next(G) Out[21]: 4 In [22]: next(G) Out[22]: 6 In [23]: next(G) Out[23]: 8 In [24]: next(G) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-24-380e167d6934> in <module>() ----> 1 next(G) StopIteration: In [25]: In [26]: G = ( x*2 for x in range(5)) In [27]: for x in G: ....: print(x) ....: 0 2 4 6 8 In [28]:
2.2 創建生成器方法2
generator非常強大。如果推算的算法比較復雜,用類似列表生成式的 for 循環無法實現的時候,還可以用函數來實現。
我們仍然用上一節提到的斐波那契數列來舉例,回想我們在上一節用迭代器的實現方式:
class FibIterator(object): """斐波那契數列迭代器""" def __init__(self, n): """ :param n: int, 指明生成數列的前n個數 """ self.n = n # current用來保存當前生成到數列中的第幾個數了 self.current = 0 # num1用來保存前前一個數,初始值為數列中的第一個數0 self.num1 = 0 # num2用來保存前一個數,初始值為數列中的第二個數1 self.num2 = 1 def __next__(self): """被next()函數調用來獲取下一個數""" if self.current < self.n: num = self.num1 self.num1, self.num2 = self.num2, self.num1+self.num2 self.current += 1 return num else: raise StopIteration def __iter__(self): """迭代器的__iter__返回自身即可""" return self
注意,在用迭代器實現的方式中,我們要借助幾個變量(n、current、num1、num2)來保存迭代的狀態?,F在我們用生成器來實現一下。
In [30]: def fib(n): ....: current = 0 ....: num1, num2 = 0, 1 ....: while current < n: ....: yield num1 ....: num1, num2 = num2, num1+num2 ....: current += 1 ....: return 'done' ....: In [31]: F = fib(5) In [32]: next(F) Out[32]: 1 In [33]: next(F) Out[33]: 1 In [34]: next(F) Out[34]: 2 In [35]: next(F) Out[35]: 3 In [36]: next(F) Out[36]: 5 In [37]: next(F) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-37-8c2b02b4361a> in <module>() ----> 1 next(F) StopIteration: done
在使用生成器實現的方式中,我們將原本在迭代器__next__
方法中實現的基本邏輯放到一個函數中來實現,但是將每次迭代返回數值的return換成了yield,此時新定義的函數便不再是函數,而是一個生成器了。簡單來說:只要在def中有yield關鍵字的 就稱為 生成器
此時按照調用函數的方式( 案例中為F = fib(5) )使用生成器就不再是執行函數體了,而是會返回一個生成器對象( 案例中為F ),然后就可以按照使用迭代器的方式來使用生成器了。
In [38]: for n in fib(5): ....: print(n) ....: 1 1 2 3 5 In [39]:
但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
In [39]: g = fib(5) In [40]: while True: ....: try: ....: x = next(g) ....: print("value:%d"%x) ....: except StopIteration as e: ....: print("生成器返回值:%s"%e.value) ....: break ....: value:1 value:1 value:2 value:3 value:5 生成器返回值:done In [41]:
總結:
- 使用了yield關鍵字的函數不再是函數,而是生成器。(使用了yield的函數就是生成器)
- yield關鍵字有兩點作用:
- 保存當前運行狀態(斷點),然后暫停執行,即將生成器(函數)掛起
- 將yield關鍵字后面表達式的值作為返回值返回,此時可以理解為起到了return的作用
- 可以使用next()函數讓生成器從斷點處繼續執行,即喚醒生成器(函數)
- Python3中的生成器可以使用return返回最終運行的返回值,而Python2中的生成器不允許使用return返回一個返回值(即可以使用return從生成器中退出,但return后不能有任何表達式)。
2.3 使用send喚醒
我們除了可以使用next()函數來喚醒生成器繼續執行外,還可以使用send()函數來喚醒執行。使用send()函數的一個好處是可以在喚醒的同時向斷點處傳入一個附加數據。
例子:執行到yield時,gen函數作用暫時保存,返回i的值; temp接收下次c.send("python"),send發送過來的值,c.next()等價c.send(None)
In [10]: def gen(): ....: i = 0 ....: while i<5: ....: temp = yield i ....: print(temp) ....: i+=1 ....:
使用send
In [43]: f = gen() In [44]: next(f) Out[44]: 0 In [45]: f.send('haha') haha Out[45]: 1 In [46]: next(f) None Out[46]: 2 In [47]: f.send('haha') haha Out[47]: 3 In [48]:
使用next函數
In [18]: f = gen() In [19]: f.__next__() Out[19]: 0 In [20]: f.__next__() None Out[20]: 1 In [21]: f.__next__() None Out[21]: 2 In [22]: f.__next__() None Out[22]: 3 In [23]: f.__next__() None Out[23]: 4 In [24]: f.__next__() None --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-24-39ec527346a9> in <module>() ----> 1 f.__next__() StopIteration:
使用__next__()
方法(不常使用)
In [18]: f = gen() In [19]: f.__next__() Out[19]: 0 In [20]: f.__next__() None Out[20]: 1 In [21]: f.__next__() None Out[21]: 2 In [22]: f.__next__() None Out[22]: 3 In [23]: f.__next__() None Out[23]: 4 In [24]: f.__next__() None --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-24-39ec527346a9> in <module>() ----> 1 f.__next__() StopIteration:
3.property屬性的使用
property屬性是一種用起來像是實例屬性一樣的特殊屬性,可以對應于某個方法。
class Foo: def func(self): pass # 定義property屬性 @property def prop(self): pass # ############### 調用 ############### foo_obj = Foo() foo_obj.func() # 調用實例方法 foo_obj.prop # 調用property屬性
property屬性的定義和調用要注意一下幾點:
- 定義時,在實例方法的基礎上添加 @property 裝飾器;并且僅有一個self參數
- 調用時,無需括號
方法:foo_obj.func() property屬性:foo_obj.prop
簡單的實例
對于京東商城中顯示電腦主機的列表頁面,每次請求不可能把數據庫中的所有內容都顯示到頁面上,而是通過分頁的功能局部顯示,所以在向數據庫中請求數據時就要顯示的指定獲取從第m條到第n條的所有數據 這個分頁的功能包括:
- 根據用戶請求的當前頁和總數據條數計算出 m 和 n
- 根據m 和 n 去數據庫中請求數據
# ############### 定義 ############### class Pager: def __init__(self, current_page): # 用戶當前請求的頁碼(第一頁、第二頁...) self.current_page = current_page # 每頁默認顯示10條數據 self.per_items = 10 @property def start(self): val = (self.current_page - 1) * self.per_items return val @property def end(self): val = self.current_page * self.per_items return val # ############### 調用 ############### p = Pager(1) p.start # 就是起始值,即:m p.end # 就是結束值,即:n
從上述可見
Python的property屬性的功能是:property屬性內部進行一系列的邏輯計算,最終將計算結果返回。
3.1 property屬性的兩種方式
- 裝飾器 即:在方法上應用裝飾器
- 類屬性 即:在類中定義值為property對象的類屬性
3.1.1 裝飾器
- 在類的實例方法上應用@property裝飾器
Python中的類有經典類和新式類,新式類的屬性比經典類的屬性豐富。( 如果類繼object,那么該類是新式類 )
經典類的實現:
class Goods: @property def price(self): return "laowang" obj = Goods() result = obj.price # 自動執行 @property 修飾的 price 方法,并獲取方法的返回值 print(result)
新式類的實現:
class Goods: """ 只有在python3中才有@xxx.setter @xxx.deleter """ def __init__(self): # 原價 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price obj = Goods() obj.price # 獲取商品價格 obj.price = 200 # 修改商品原價 del obj.price # 刪除商品原價
總結:
- 經典類中的屬性只有一種訪問方式,其對應被 @property 修飾的方法
- 新式類中的屬性有三種訪問方式,并分別對應了三個被@property、@方法名.setter、@方法名.deleter修飾的方法
3.1.2 類屬性方式
當使用類屬性的方式創建property屬性時,經典類和新式類無區別。
class Foo: def get_bar(self): return 'laowang' BAR = property(get_bar) obj = Foo() reuslt = obj.BAR # 自動調用get_bar方法,并獲取方法的返回值 print(reuslt)
property方法中有個四個參數
- 第一個參數是方法名,調用 對象.屬性 時自動觸發執行方法
- 第二個參數是方法名,調用 對象.屬性 = XXX 時自動觸發執行方法
- 第三個參數是方法名,調用 del 對象.屬性 時自動觸發執行方法
- 第四個參數是字符串,調用 對象.屬性.doc ,此參數是該屬性的描述信息
class Foo(object): def get_bar(self): print("getter...") return 'laowang' def set_bar(self, value): """必須兩個參數""" print("setter...") return 'set value' + value def del_bar(self): print("deleter...") return 'laowang' BAR = property(get_bar, set_bar, del_bar, "description...") obj = Foo() obj.BAR # 自動調用第一個參數中定義的方法:get_bar obj.BAR = "alex" # 自動調用第二個參數中定義的方法:set_bar方法,并將“alex”當作參數傳入 desc = Foo.BAR.__doc__ # 自動獲取第四個參數中設置的值:description... print(desc) del obj.BAR # 自動調用第三個參數中定義的方法:del_bar方法
總結:
- 定義property屬性共有兩種方式,分別是【裝飾器】和【類屬性】,而【裝飾器】方式針對經典類和新式類又有所不同。
- 通過使用property屬性,能夠簡化調用者在獲取數據的流程。
原文鏈接:https://blog.csdn.net/weixin_43889788/article/details/128374840
相關推薦
- 2022-12-13 Dart多態控制反轉編碼規范實例詳解_Dart
- 2022-08-10 C#結束進程及子進程_C#教程
- 2022-05-11 C#對集合進行排序_C#教程
- 2022-09-14 python?特殊屬性及方法詳細解析_python
- 2023-03-03 詳解C++?STL模擬實現forward_list_C 語言
- 2022-12-13 C++?Boost?Pool超詳細講解_C 語言
- 2023-07-02 jQuery和HTML對某個標簽設置只讀或者禁用屬性的方式_jquery
- 2023-12-16 SpringBoot 配置文件使用@ @取值
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支