迭代器和異常處理
1. 可迭代對象
迭代:迭代即更新換代,每次的更新都必須依賴於上一次的結果.
每一次對過程的重復稱為一次“迭代”,每一次迭代得到的結果會被用來作為下一次迭代的初始值.
在python
中可以通過for
循環來遍歷這個list
或tuple
,這種遍歷我們稱為迭代(Iteration)。
迭代其實給我們提供了一種不依賴索引取值的方式
1.1 可迭代對象
內置有__iter__
方法的都稱之為可迭代對象.
針對雙下滑線開頭 雙下滑線結尾的方法 最為專業標准的讀法為
雙下 方法名
查找哪些類型是可迭代對象
# 1. 整型:
>>> i = 1
>>> i.__iter__() # 沒有__iter__方法,不是可迭代對象
AttributeError: 'int' object has no attribute '__iter__'
# 2. 浮點型:
>>> f=1.2
>>> f.__iter__() # 沒有__iter__方法,不是可迭代對象
AttributeError: 'float' object has no attribute '__iter__'
# 3. 布爾值
>>> bl = True
>>> bl.__iter__() # 沒有__iter__方法,不是可迭代對象
AttributeError: 'bool' object has no attribute '__iter__'
# 4. 字符串
>>> str = 'abc'
>>> str.__iter__() # 有__iter__方法,是可迭代對象
<str_iterator object at 0x7ffa1ac28898>
# 5. 列表
>>> list1 = [1, 2, 3, 4]
>>> list1.__iter__() # 有__iter__方法,是可迭代對象
<list_iterator object at 0x7ffa1ac28860>
# 6. 元組
>>> tuple1 = (1, 2, 3, 4)
>>> tuple1.__iter__() # 有__iter__方法,是可迭代對象
<tuple_iterator object at 0x7ffa1ac286d8>
# 7. 字典
>>> dict1 = {'name':'hans', 'age':18}
>>> dict1.__iter__() # 有__iter__方法,是可迭代對象
<dict_keyiterator object at 0x7ffa1ac2e7c8>
# 8. 集合
>>> set1 = {1, 2, 3, 4}
>>> set1.__iter__() # 有__iter__方法,是可迭代對象
<set_iterator object at 0x7ffa1ac0fc60>
# 9. 文件對象
>>> f = open(r'a.txt', 'r', encoding='utf8')
>>> f.__iter__()
<_io.TextIOWrapper name='a.txt' mode='r' encoding='utf8'>
可迭代對象為:字符串、列表、元組、字典、集合、文件對象
其實__iter__()
有更簡單的調用方式:iter()
# 列表:
>>> list1 = [1, 2, 3, 4]
>>> list1.__iter__()
<list_iterator object at 0x7ffa1ac28860>
>>> iter(list1)
<list_iterator object at 0x7ffa1ac28400>
# 字典:
>>> dict1 = {'name':'hans', 'age':18}
>>> dict1.__iter__() # 有__iter__方法,是可迭代對象
<dict_keyiterator object at 0x7ffa1ac2e7c8>
>>> iter(dict1)
<dict_keyiterator object at 0x7ffa1ac2e7c8>
# __iter__()和iter()返回的結果是一樣的。
2.迭代器對象
現在我們知道可迭代對象為:字符串、列表、元組、字典、集合、文件對象
但這些是可迭代對象,但不一定是迭代器對象。
因為,迭代器是:即含有__iter__
方法又含有__next__
方法的才叫迭代器。
查看可迭代對象是否為迭代器:
# 1. 字符串
>>> str = 'abc'
>>> str.__next__() # 沒有__next__方法,不是迭代器
AttributeError: 'str' object has no attribute '__next__'
# 2. 列表
>>> list1 = [1, 2, 3, 4]
>>> list1.__next__() # 沒有__next__方法,不是迭代器
AttributeError: 'list' object has no attribute '__next__'
# 3. 元組
>>> tuple1 = (1, 2, 3, 4)
>>> tuple1.__next__() # 沒有__next__方法,不是迭代器
AttributeError: 'tuple' object has no attribute '__next__'
# 4. 字典
>>> dict1 = {'name':'hans', 'age':18}
>>> dict1.__next__() # 沒有__next__方法,不是迭代器
AttributeError: 'dict' object has no attribute '__next__'
# 5. 集合
>>> set1 = {1, 2, 3, 4}
>>> set1.__next__() # 沒有__next__方法,不是迭代器
AttributeError: 'set' object has no attribute '__next__'
# 6. 文件對象
>>> f = open(r'a.txt', 'r', encoding='utf8')
>>> f.__next__() # 有__next__方法,是迭代器
'Hello\n'
可迭代對象字符串、列表、元組、字典、集合、文件對象中只有文件對象是迭代器。
文件對象
本身即是可迭代對象
又是迭代器對象
雖然字符串、列表、元組、字典、集合不是迭代器,但是可以使用__iter__
方法轉換成迭代器
# 1. 字符串
>>> str = 'abc'
>>> s = str.__iter__() # 將迭代對象使用__iter__方法轉成迭代器對象
>>> s.__iter__() # s即有__iter__方法又有__next__方法
<str_iterator object at 0x7ffa1ac28c18>
>>> s.__next__() # 迭代器對象執行__next__方法其實就是在迭代取值(類似for循環)
'a'
>>> s.__next__()
'b'
>>> s.__next__()
'c'
# 2. 列表
>>> list1 = [1, 2, 3, 4]
>>> l.__next__()
1
>>> l.__next__()
2
>>> l.__next__()
3
>>> l.__next__()
4
>>> l.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
# 同理元組、字典、集合都可以使用這種方法轉成迭代器。
迭代器對象執行__next__
方法其實就是在迭代取值,但是它不知道序列的長度,所以會__next__
會一直取,直到沒有數據時拋出StopIteration
錯誤。
迭代器對象無論執行多少次__iter__方法,還是迭代器對象(本身)
>>> list1 = [1, 2, 3, 4]
>>> l = list1.__iter__()
>>> l.__iter__()
<list_iterator object at 0x7ffa1ac286a0>
>>> l.__iter__().__iter__().__iter__()
<list_iterator object at 0x7ffa1ac286a0>
# 執行一次__iter__方法和多次執行__iter__方法,結果一樣。
練習
>>> dict1 = {'name':'hans', 'age':18}
>>> d = dict1.__iter__()
>>> d.__iter__().__next__() # 猜一下運行結果
'name'
>>> d.__iter__().__next__()
'age'
# 上面就是體現了,迭代器對象無論執行多少次__iter__方法,還是迭代器對象(本身)
>>> dict1.__iter__().__next__()
>>> dict1.__iter__().__next__()
>>> dict1.__iter__().__next__()
# 猜一下上面執行的結果
# 結果:
'name'
'name'
'name'
# 每一次都會重新轉換成迭代器。然后進行一次迭代取值
3. for循環本質
循環打印出列表中每個元素, 但是不能使用for
循環
l1 = [1,2,3,4,5,6,7,8,9,11,22,33,44,55]
res = l1.__iter__()
count = 0
while count < len(l1):
print(res.__next__())
count +=1
# 執行結果:
1
2
3
4
5
6
7
8
9
11
22
33
44
55
其實for
循環本質就是使用迭代器
for循環內部原理
1. 將關鍵字`in`后面的數據先調用`__iter__`方法轉為迭代器對象
2. 循環執行`__next__`方法
3. 取完之后`__next__`會報錯 但是`for`循環會自動捕獲該錯誤並處理
4. 異常處理
4.1 什么是異常
代碼運行出錯會導致異常 異常發生后如果沒有解決方案則會到底整個程序結束
4.2 異常三個重要組成部分
-
traceback
翻到最下面從下往上的第一個藍色字體鼠標左鍵點擊即可跳轉到錯誤的代碼所在的行 -
xxxError
異常的類型
-
異常類型冒號后面的內容
異常的詳細原因(仔細看完之后可能就會找到解決的方法)
4.3 異常的種類
- 語法錯誤
- 邏輯錯誤
4.4 常見異常類型
異常 | 描述 |
---|---|
AssertionError | assert(斷言)語句失敗 |
AttributeError | 試圖訪問一個對象沒有的屬性,比如foo.x ,但是foo沒有x這個屬性。 |
IOError | 輸入/輸出異常,基本上是無法打開文件。 |
ImportError | 無法引入模塊或者包,基本上是路徑問題 |
IndentationError | 語法錯誤,代碼沒有正確對齊 |
IndexError | 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5] |
KeyError | 試圖訪問字典里不存在的鍵 |
KerboardInterrupt | Ctrl + C 被按下 |
NameError | 使用一個還未被賦值予對象的變量 |
SyntaxError | Python代碼非法,代碼不能解釋 |
TypeError | 傳入對象類型與要求的不符 |
UnboundLocalError | 試圖訪問一個還未被設置的局部變量,基本上是由於另一個同名的全局變量,導致你以為正在訪問它 |
ValueError | 傳入一個調用者不期望的值,即使值的類型是正確的 |
[Python官方異常列表:][https://docs.python.org/3/library/exceptions.html#exception-hierarchy ]
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
4.5 異常處理基本語法結構
格式:
try:
有可能會出錯的代碼
except 異常類型 as e:
出錯之后對應的處理機制(e是錯誤的詳細信息)
except 異常類型 as e:
出錯之后對應的處理機制(e是錯誤的詳細信息)
except 異常類型 as e:
出錯之后對應的處理機制(e是錯誤的詳細信息)
示例:
# 代碼:
f = open(r'b.txt', 'r', encoding='utf8')
print("Hello")
# 執行結果:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'b.txt'
# 沒有b.txt這個文件,程序報錯退出,后面的print("Hello")沒有執行
# 使用異常處理
# 代碼:
try:
f = open(r'b.txt', 'r', encoding='utf8')
except FileNotFoundError as e:
print("沒有這個文件,請先創建它")
print("Hello")
# 執行結果:
沒有這個文件,請先創建它
Hello
# 雖然也報錯,但是捕獲到異常,所以程序沒有報錯退出print()依然執行
使用異常處理時,except
可以寫多個,寫多個會比較麻煩,可以使用Exception
它能捕獲各種異常
# 代碼:
try:
f = open(r'b.txt', 'r', encoding='utf8')
except Exception as e: # 使用Exception依然能捕獲到異常
print("沒有這個文件,請先創建它")
# 執行結果:
沒有這個文件,請先創建它
使用異常處理,循環打印出列表中每個元素, 但是不使用for
循環
l1 = [1,2,3,4,5,6,7,8,9,11,22,33,44,55]
def foo(l):
res = l.__iter__()
while True:
try:
print(res.__next__())
except StopIteration as e:
break
foo(l1)
4.5.2 異常使用else
異常也可以使用else
# 代碼1:
list_num = [1, 2, 3, 4, 5]
try:
print(list_num[10]) # 超過索引范圍
except IndexError as e:
print("Not fond")
else:
print("no error")
# 執行結果:
Not fond
# 發現沒有執行else里面的代碼。
# 代碼2:
list_num = [1, 2, 3, 4, 5]
try:
print(list_num[1]) # 沒有超過索引范圍
except IndexError as e:
print("Not fond")
else:
print("no error")
# 執行結果:
2
no error
# 執行了 else
所以else
是被監測的代碼不報錯的時候執行。
4.5.3 異常使用finally
finally
是無論被監測的代碼是否報錯最終都會執行
# 有異常,代碼
list_num = [1, 2, 3, 4, 5]
try:
print(list_num[10]) # 超過索引范圍
except IndexError as e:
print("Not fond")
else:
print("no error")
finally:
print("都會執行")
# 執行結果:
Not fond
都會執行
# 無異常,代碼:
list_num = [1, 2, 3, 4, 5]
try:
print(list_num[1])
except IndexError as e:
print("Not fond")
else:
print("no error")
finally:
print("都會執行")
# 執行結果:
2
no error
都會執行
4.5.4 斷言assert
斷言可以在條件不滿足程序運行的情況下直接返回錯誤,而不必等待程序運行后出現崩潰的情況。
用於判斷一個表達式,在表達式條件為 false
的時候觸發異常。
>>> 1 == 1
True
>>> 1 == 2 # 為假則返回False
False
>>> assert 1 == 1
>>> assert 1 == 2 # 使用assert為假則報錯
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
4.5.5 自定義異常raise
raise 語句拋出一個指定的異常。
raise 唯一的一個參數指定了要被拋出的異常。它必須是一個異常的實例或者是異常的類
>>> x = 5
>>> if x > 4:
... raise Exception('%s 不能大於4' % x)
raceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: 5 不能大於4
>>> if x > 4:
raise IndexError ('%s 不能大於4' % x)
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: 5 不能大於4
raise
的錯誤類型可以自己定義,或者直接寫Exception
, 這個自己定義的類型要在python
錯誤類型中。
4.5.6 示例:用while
使用迭代器模擬for
list_num = [1, 2, 3, 4, 5]
res = list_num.__iter__()
while True:
try:
print(res.__next__())
except StopIteration as e:
break
4.5.7 自定義異常
我們自己可以通過創建新的異常類命名自己的異常,論是以直接還是間接的方式,異常都應從 Exception
類派生,也有從BaseException
類派生,BaseException
是為系統退出異常而保留的,而且Exception
是繼承了BaseException`.
class MyError(Exception):
def __init__(self,msg):
self.msg = msg
def __str__(self):
return self.msg
raise MyError("出現了異常")
# 執行:
Traceback (most recent call last):
raise MyError("出現了異常")
__main__.MyError: 出現了異常
# class MyError(BaseException):
4.6 使用異常處理規則
- 有可能會出現錯誤的代碼才需要被監測
- 被監測的代碼一定要越少越好
- 異常捕獲使用頻率越低越好