在程序運行過程當中,總會遇到各類各樣的錯誤。有的錯誤是程序編寫有問題形成的,好比原本應該輸出整數結果輸出了字符串,有的錯誤是用戶輸入形成的,好比讓用戶輸入email地址,結果獲得一個空字符串,這種錯誤能夠經過檢查用戶輸入來作相應的處理。還有一類錯誤是徹底沒法在程序運行過程當中預測的,好比寫入文件的時候,磁盤滿了,寫不進去了,或者從網絡抓取數據,網絡忽然斷掉了。這類錯誤也稱爲異常,在程序中一般是必須處理的,不然,程序會由於各類問題終止並退出。linux
在Python中產生異常主要有兩種方式:解釋器出觸發和手動觸發編程
當異常被觸發後,觸發異常的代碼段的後續代碼將不會被繼續執行。若是存在於頂層命名空間中,可能還會致使整個程序退出。json
什麼狀況下解釋器會觸發異常?舉個例子:網絡
print('hello world) print(1/0)
當解釋器一行一行解釋時,因爲print函數少些一個引號,不符合Python語法規範,因此會提示SyntaxError,嚴格來講的話,語法錯誤不算一種異常,在解釋階段就沒法經過,因此沒辦法進行捕獲,其餘的還好比IndentationError,由於是SyntaxError的子類, 而print(1/0)的除數不能爲零,符合Python語法規範,只是在代碼運行時觸發了ZeroDivisionError異常,這種在運行時觸發的異常,咱們都是能夠捕獲的。編程語言
在代碼中,使用raise
關鍵字,能夠手動觸發異常,當檢測某些數據類型,不符合預期時,就能夠手動的觸發,它的格式以下:模塊化
raise 異常類型 raise 異常類型() raise 自定義異常 raise 自定義異常()
手動觸發異常:函數
def add(x,y): if isinstance(x, int) or isinstance(y, int): raise TypeError return x + y
函數add期待兩個類型爲int的參數,若是咱們傳遞'a','1'時,因爲Python語言的特性,預期的結果就可能會不一樣,若是後續代碼依賴該函數的返回值,那麼就可能會引起後續代碼的異常報錯,不利於排查。
raise關鍵字能夠觸發異常類,也能夠觸發異常類實例,其實當咱們觸發異常類時,實際上raise會幫咱們實例化,而後觸發。異常類也能夠傳參,其本質上,能夠理解爲:測試
raise Exception == raise Exception() class Exception: def __init__(self, message='Exception', code=200): self.message = message self.code = code ......
不傳遞參數時,使用默認屬性初始化,傳遞參數時,則參數進行實例初始化賦值。ui
Python內置了大量的異常類,用於標識不一樣的異常以及分類。以下
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
不一樣的層級表示繼承關係,經常使用的錯誤及其含義以下:
AttributeError # 試圖訪問一個對象沒有的屬性時 IOError # 輸入/輸出異常;基本上是沒法打開文件 ImportError # 沒法引入模塊或包;基本上是路徑問題或名稱錯誤 SyntaxError # 語法錯誤 IndentationError # 代碼沒有正確對齊(SyntaxError的子類) IndexError # 下標索引超出序列邊界,好比當x只有三個元素,卻試圖訪問x[5] KeyError # 試圖訪問字典裏不存在的鍵 KeyboardInterrupt # Ctrl+C被按下 NameError # 使用一個還未被賦予對象的變量 TypeError # 傳入對象類型與要求的不符合 UnboundLocalError # 試圖訪問一個還未被設置的局部變量,基本上是因爲另有一個同名的全局變量,致使你覺得正在訪問它 ValueError # 傳入一個調用者不指望的值,即便值的類型是正確的,好比列表a=[1, 2, 3],而你執行了a.remove(4) SystemExit # sys.exit()函數觸發的異常,異常被解釋器發現時,會退出執行 ArithmetieError # 全部算數引起的異常 LookupError # 使用映射的鍵或序列的索引無效的異常
Python提供的大部分運行時的錯誤異常,都繼承自Exception類,因此咱們自定義的異常類,也要從Exception類繼承(固然也能夠從BaseException繼承,但不建議。)
自定義的異常類須要從Exception進行繼承
class MyException(Exception): def __init__(self,message='Not Exception',code=200): super().__init__(message,code) # 就等於 # class MyException(Exception): # pass try: raise MyException('from MyException') raise MyException except: print('沒有異常')
因爲異常有默認值,因此傳遞不傳遞參數均可
所謂異常處理就是:出現異常,捕獲並處理異常。一個健壯的程序應該儘量的的避免錯誤,儘量的捕捉錯誤,處理各類異常。Python內置了一套異常處理機制,來幫助咱們進行錯誤處理。其完整格式以下:
try: 代碼段1 # 檢測執行的代碼段 except: 代碼段2 # try代碼段產生異常後,被捕捉到後執行的代碼段 else: 代碼段3 # try代碼段中沒有異常要執行的代碼段(非必須) finally: 代碼段4 # try代碼段中無論有沒有異常,都要執行的代碼段(非必須)
其中except語句能夠有多個,捕捉不一樣類型的異常並進行相應的處理,而且還可利用as語句存儲異常,因此except的擴展格式:
except: 表示遇到異常就捕捉。 except Exception: 表示只捕捉Exception類的異常,而Exception類包含了Python內置的全部異常,因此功能相似於捕捉全部。 except IndexError as e: 捕捉IndexError異常,並交給變量e存儲。
須要注意的是: except子句必須和try在同一層,搭配使用,不能單獨使用except
。下面來看一個小例子:
try: a=[1,2,3] a[4] except: print('一切正常',e) else: print('真的是一切正常') finally: print('我是 finally')
分析:
因爲列表a只有3個元素,取列表第四個元素,會報異常(IndexError: list index out of range,從pycharm的console窗口就能看到),異常被except捕捉,因此會打印,一切正常,並打印錯誤信息。因爲產生了異常不會執行else語句,最後執行finally語句。
實際上咱們一段代碼可能會有不一樣類型的錯誤,而咱們須要針對不一樣類型的錯誤,作不一樣的操做,因此這個時候就會用到多except語句。
try: a = [1,2,3,4] a[7] except IndexError as e: print('索引錯誤',e) except KeyError as e: print('key錯誤',e ) except: print('發生異常了')
分析:
異常被IndexError捕獲,因此會打印索引錯誤。
多條except語句,優先級從上到下,和if條件的優先級是相同的,即若是捕獲到一個,就不繼續執行except語句了。利用多except語句,來對不一樣的異常進行處理,可使咱們的程序更健壯。
先來看下面的例子:
def test(x, y): try: print('from Test') return x / y finally: return 200 a = test(1,0) print(a) # from Test # 200
分析:
在test語句中因爲除數爲0,因此會觸發ZeroDivisionError異常,因爲finally是最後被執行的,尚未上報呢,就return了,這個時候異常就被壓制了。
函數的返回值取決於最後一個執行的return語句,finally是最後被執行的語句。因此在finally語句中return是一個不明智的決定,除非你真的須要它。
當異常沒有被處理時,就會向上層逐級上報。
def foo1(): foo2() def foo2(): foo3() def foo3(): raise Exception foo1() # Traceback (most recent call last): # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 15, in <module> # foo1() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 7, in foo1 # foo2() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 10, in foo2 # foo3() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 13, in foo3 # raise Exception # Exception
異常在foo3中產生,上報到foo2中,foo2沒有處理,上報到foo1中,foo1沒有處理,繼續上報,若是到了外層尚未被處理,就會中斷異常所在的線程的執行。
try ... except 能夠被嵌套使用
咱們能夠在可能產生異常地方當即捕獲,或者在邊界捕獲。當即捕獲很好理解,那什麼是邊界捕獲呢?
try的工做原理:
通常來講,編程語言中,庫,包,模塊是同一種概念、是代碼的組織方式。Python中只有一種模塊對象類型,可是爲了模塊化組織模塊的便利,提供了'包'的概念。
Pythong主要提供了兩類導入模塊的方法。它們是import和from ... import。
語句 | 含義 |
---|---|
import n1,n2 | 徹底導入 |
import x as x | 模塊別名徹底導入 |
導入過程:
import os print(list(filter(lambda name: not name.startswith('_'),dir()))) # 去掉_開頭的方法 # ['os']
dir()返回當前名稱空間內的名稱。
(1) import functools as ft print(list(filter(lambda name:not name.startswith('_'),dir()))) # ['ft'] (2) import os print(os) # <module 'os' from 'D:\\Python3.6.6\\lib\\os.py'> print(os.path) # <module 'ntpath' from 'D:\\Python3.6.6\\lib\\ntpath.py'> print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['os']
當咱們使用as語句時,那麼當前名稱空間會僅存在於一個as後的別名,來指向模塊了。os.path在打印時也是一個模塊,那麼咱們可否只導入os模塊下的path模塊呢?
import os.path print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['os']
並無導入os.path模塊,只有os模塊,這是爲何呢?
若是使用了as,as後的名稱直接綁定到導入的模塊對象上,並將該名稱加入到本地命名空間中。
import 後面只能是包名,或者模塊名
語句 | 含義 |
---|---|
from xx import xx | 部分導入 |
from xx import xx as xx | 別名部分導入 |
導入過程:
from os import path
print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['path']
from os import path as osp
print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['osp', 'path']
```
from 後面只能是模塊或者包名,import後面能夠是包、模塊、類、函數、甚至是某個變量
一個py文件就是一個模塊。先不考慮模塊查找順序,看下面例子:
# 目錄結構 # Learn_Python_fromn_Zero # |--my_module.py # |--my_script.py (1) my_module.py class MyClass: def __init__(self, name): self.name = name def print(self): print('{} say hello'.format(self.name)) def my_func(context): print(context) (2) my_script.py import my_module daxin = my_module.MyClass('daxin') daxin.print() # daxin say hello
固然也能夠經過from方式只導入某個類
from my_module import MyClass daxin = MyClass('daxin') daxin.print() # daxin say hello
須要注意的是,自定義模塊的名稱要遵循必定的規範:
sys.path是一個列表,裏面存放的就是模塊搜索的路徑
import sys print(*sys.path,sep='\n') # E:\Learn_Python_from_Zero\chapter08\_Package_and_module # E:\Learn_Python_from_Zero # D:\Python3.6.6\python36.zip # D:\Python3.6.6\DLLs # D:\Python3.6.6\lib # D:\Python3.6.6 # D:\Python3.6.6\lib\site-packages
上面的結果就是我當前環境下Python模塊的路徑搜索順序,當加載一個模塊的時候,須要從這些路徑中從前到後依次查找,但並不搜索這些目錄的子目錄,搜索到模塊就加載,搜索不到就拋異常。
大體的路徑順序爲:
程序主目錄
,運行程序的主程序所在的目錄PYTHONPATH目錄
,環境變量PYTHONPATH設置的目錄,也是搜索模塊哦路徑標準庫目錄
,Python自帶的庫模塊所在的目錄
sys.path是一個列表,那麼就意味着能夠進行增刪改查,不過通常咱們不會操做這個路徑,除非你真的須要。
咱們說模塊的導入就是一個加載以及關聯到標識符存入本地命名空間的過程,若是我要屢次導入相同的模塊,是否是要屢次加載,屢次從新存入本地命名空間呢,寫個代碼測試一下
import os print(id(os)) import os as daxin print(id(daxin)) import os print(id(os)) # 2300567923272 # 2300567923272 # 2300567923272
三次導入的模塊的內存地址是相同的,說明只會導入一次,這是爲何呢?這是由於在Python中,全部的加載的模塊都會記錄在sys.modules中,它裏面存放的是已經加載過的全部模塊的字典,導入模塊就和它有關:
每一個模塊都會定義一個__name__特殊變量來存儲當前模塊的名稱,若是不指定,默認爲源碼文件名,若是是包則有限定名(包名.文件名)。
解釋器初始化的時候,會初始化sys.modules字典,用來保存已加載的模塊,加載builtins(全局函數、常量)模塊,__main__模塊,sys模塊,以及初始化模塊搜索路徑sys.path
當從標準輸入(命令行方式敲代碼)、腳本(在命令行運行)或者交互式讀取的時候,會將模塊的__name__設置爲__main__,模塊的頂層代碼就在__main__這個做用域中執行。若是是import導入的,其__name__默認就是模塊的名稱。
(1) mymodule.py print(__name__) (2) myscript.py import mymodulel.py
有如上兩個py文件存在於同一層級下:
不少時候,咱們寫的函數,類須要在其餘地方調用,導入時會自動執行模塊,若是存在不少print語句時,就會直接在導入的地方進行輸出,不便於調用。因此通常咱們都使用下面結構:
if __name__ == '__main__': pass
他的好處是:
屬性 | 含義 |
---|---|
__file__ | 當前文件的絕對路徑(字符串格式) |
__cached__ | 編譯後的字節碼文件路徑(字符串格式) |
__sepc__ | 顯示模塊的規範 |
__name__ | 模塊名 |
__package__ | 當模塊是包時,同__name__,不然能夠設置爲頂級模塊的空字符串。 |
Python中把包理解爲一個特殊的模塊,在文件系統上的體現就是一個目錄。用於組織多個模塊或者其餘更多的包。主要有兩種格式:
目錄下包含一個__init__.py文件
新版本的Python直接認爲一個目錄就是一個包,但老版本的包,下面須要存在一個__init__.py文件,因此規範化起見,建議建立這個文件。
__init__.py文件的做用:
在linux下,一切皆文件,那麼一個目錄也是一個文件,Python認爲包文件自己也能夠有必定的屬性或者其餘信息,因此在包下用init.py標識這個包自身的屬性信息或者其餘代碼。包在導入時,它下面的__init__.py 就會被執行
包目錄下的其餘py文件、子目錄都被稱爲它的子模塊。
# 目錄結構: ---- mypackage |--- __init__.py |--- hello |--- __init__.py |--- mymodule.py ---- myscript.py # myscript.py import mypackage print(mypackage.hello.mymodule) # ? 能夠執行嗎
不能夠執行,咱們說可否執行主要看sys.modules中是否加載,以及是否關聯到本地命名空間中去
import mypackage print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'mypackage'] print(dir(mypackage)) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
從命名空間中,咱們能夠看到,mypackage包下並無hello這個包(都沒有加載到sys.modules中去),因此說直接經過import mypackage是沒法導入它下面的子包以及子模塊的。
import mypackage.hello.mymodule import sys print(sys.modules.keys()) # [ ..., 'mypackage', 'mypackage.hello', 'mypackage.hello.mymodule'] print(dir()) # [ ... 'mypackage', 'sys'] mypackage.hello.mymodule.echo('hello world')
從命名空間中,咱們看到只有頂級包名加入了本地命名空間,可是咱們導入的hello,mymodule已經加載到了sys.modules中,這樣咱們就可使用mypackage調用了。
from json import encoder json.dump可使用嗎? # 沒法使用,由於雖然加載了json模塊,可是僅僅把encoder導入了當前名稱空間 import json.encoder json.dump可使用嗎? # 可使用,由於eocoder是一個模塊,這種導入當時,只會將頂層json模塊加入到本地名稱空間中,因此能夠經過json執行dump函數。
絕對導入:在import語句或者from語句中,導入模塊。模塊名稱最前面不是以.點開頭的。絕對導入老是去模塊搜索路徑中尋找(也會查看是否已經加載)
相對導入:只能在包內使用,且只能用在from語句中,使用.點號,表示當前目錄內。使用..表示上一級目錄。
不要在頂層模塊中使用相對導入。
--- package1 | --- m1.py | --- m2.py --- m3.py # m2.py from . import m1 # m3.py from package1 import m2
直接運行m2.py時,是沒法導入m1的。而,經過m3,導入m2,是正常的。這是由於:一旦一個模塊中使用了相對導入,就不能夠做爲主模塊運行了
。
使用相對導入的模塊就是爲了內部相互引用資源的,不是爲了直接運行的,對於包來講,正確的使用方式仍是在頂級模塊使用這些包。
模塊中的變量、類、函數等等均可以稱做模塊的屬性,在導入的時候,均可以被直接導入到本地命名,只要它們符合一個合法的標識符便可(包名,也一樣適用)
# mymodule.py _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py import mymodule print(dir(mymodule)) # ['A','__x', '_a', 'hello', 'y']
也就是說模塊內沒有私有變量,在模塊定義中不作特殊處理(類的私有變量會被更名)
使用from xxx import * 導入時,xxx模塊中的私有屬性以及保護屬性將不會被導入。
# mymodule.py _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py from mymodule import * print(dir()) # ['A','__x', '_a', 'hello', 'y']
通常寫在文件的前幾行,用於表示當from xxx import * 導入本模塊時,哪些屬性能夠被別人導入,它的值是一個列表.
# mymodule.py __all__ = ['_a','y] _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py from mymodule import * print(dir()) # ['_a', 'y']
因此使用from xxx import * 導入時: