29 - 異常處理-模塊化
目錄
- 1 異常
- 1.1 產生異常
- 1.1.1 解釋器觸發異常
- 1.1.2 手動觸發異常
- 1.2 異常類型
- 1.2.1 自定義異常
- 1.3 異常處理(捕獲)
- 1.3.1 多種捕獲
- 1.3.2 finally子句引發的問題
- 1.3.3 異常的傳遞
- 1.3.4 異常的捕捉時機
- 1.3.5 小結
- 1.1 產生異常
- 2 模塊化
- 2.1 導入語句
- 2.1.1 import導入
- 2.1.2 from導入
- 2.2 自定義模塊
- 2.3 模塊搜索順序
- 2.4 模塊的重復導入
- 2.5 模塊的運行
- 2.6 包
- 2.6.1 模塊和包的區別
- 2.7 相對導入與絕對導入
- 2.8 訪問控制
- 2.8.1 from xxx import *
- 2.8.2 __all__
- 2.1 導入語句
1 異常
????????在程序運行過程中,總會遇到各種各樣的錯誤。有的錯誤是程序編寫有問題造成的,比如本來應該輸出整數結果輸出了字符串,有的錯誤是用戶輸入造成的,比如讓用戶輸入email地址,結果得到一個空字符串,這種錯誤可以通過檢查用戶輸入來做相應的處理。還有一類錯誤是完全無法在程序運行過程中預測的,比如寫入文件的時候,磁盤滿了,寫不進去了,或者從網絡抓取數據,網絡突然斷掉了。這類錯誤也稱為異常,在程序中通常是必須處理的,否則,程序會因為各種問題終止並退出。
1.1 產生異常
在Python中產生異常主要有兩種方式:解釋器出觸發和手動觸發
當異常被觸發後,觸發異常的代碼段的後續代碼將不會被繼續執行。如果存在於頂層命名空間中,可能還會導致整個程序退出。
1.1.1 解釋器觸發異常
什麽情況下解釋器會觸發異常?舉個例子:
print('hello world)
print(1/0)
????????當解釋器一行一行解釋時,由於print函數少些一個引號,不符合Python語法規範,所以會提示SyntaxError,嚴格來說的話,語法錯誤不算一種異常,在解釋階段就無法通過,所以沒辦法進行捕獲,其他的還比如IndentationError,因為是SyntaxError的子類, 而print(1/0)的除數不能為零,符合Python語法規範,只是在代碼運行時觸發了ZeroDivisionError異常,這種在運行時觸發的異常,我們都是可以捕獲的。
1.1.2 手動觸發異常
在代碼中,使用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
......
不傳遞參數時,使用默認屬性初始化,傳遞參數時,則參數進行實例初始化賦值。
1.2 異常類型
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繼承,但不建議。)
1.2.1 自定義異常
自定義的異常類需要從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('沒有異常')
由於異常有默認值,所以傳遞不傳遞參數都可
1.3 異常處理(捕獲)
????????所謂異常處理就是:出現異常,捕獲並處理異常。一個健壯的程序應該盡可能的的避免錯誤,盡可能的捕捉錯誤,處理各種異常。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語句。
1.3.1 多種捕獲
????????實際上我們一段代碼可能會有不同類型的錯誤,而我們需要針對不同類型的錯誤,做不同的操作,所以這個時候就會用到多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語句,來對不同的異常進行處理,可以使我們的程序更健壯。
1.3.2 finally子句引發的問題
先來看下面的例子:
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是一個不明智的決定,除非你真的需要它。
1.3.3 異常的傳遞
當異常沒有被處理時,就會向上層逐級上報。
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 可以被嵌套使用
1.3.4 異常的捕捉時機
我們可以在可能產生異常地方立即捕獲,或者在邊界捕獲。立即捕獲很好理解,那什麽是邊界捕獲呢?
- 例如:寫了一個模塊,用戶調用這個模塊的時候捕獲異常,模塊內部需要捕獲、處理異常,一旦內部處理了,外部調用者就無法感知了。
- 例如:open函數,出現的異常交給調用者處理,如果打開的文件不存在,可以創建,文件存在了,就不再創建了,繼續看是否修改又或者刪除,又或者其他業務邏輯。
- 例如:自己寫一個類,使用了open函數,但是出現了異常不知道如何處理,就繼續向外層拋出,一般來說最外城也是邊界,必須處理這個異常了,否則線程退出。
1.3.5 小結
try的工作原理:
- 如果try中語句執行時發生異常,搜索except子句,並執行第一個匹配該異常的except子句
- 如果沒有匹配的except子句,異常將被傳遞到外層的try(如果有),如果外層不處理這個異常,將繼續向外拋出,到達最外層,還沒有被處理,就終止異常所在的線程
- 如果在try執行時沒有發生異常,將執行else子句中的代碼段
- 無論try中是否發生異常,finally子句都將會被執行
2 模塊化
一般來說,編程語言中,庫,包,模塊是同一種概念、是代碼的組織方式。Python中只有一種模塊對象類型,但是為了模塊化組織模塊的便利,提供了‘包‘的概念。
- 模塊module:指的是一個個Python的源代碼文件
- 包package:指的是模塊組織在一起的包名同名的目錄及其相關文件
2.1 導入語句
Pythong主要提供了兩類導入模塊的方法。它們是import和from ... import。
2.1.1 import導入
語句 | 含義 |
---|---|
import n1,n2 | 完全導入 |
import x as x | 模塊別名完全導入 |
導入過程:
- 找到指定的模塊,加載和初始化它,生成模塊對象。找不到,拋出異常
- 在import所在的作用域的局部命名空間中,增加名稱與上一步創建的對象關聯。
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 後面只能是包名,或者模塊名
2.1.2 from導入
語句 | 含義 |
---|---|
from xx import xx | 部分導入 |
from xx import xx as xx | 別名部分導入 |
導入過程:
- 找到from子句中指定的模塊,加載並初始化它們(不是導入)
- 對於import 子句後的名稱
- 先查from子句加載的模塊是否具有該名稱的屬性
- 如果不是,則嘗試導入該名稱的子模塊
- 還沒有找到,則拋出ImportError異常
- 這個名稱保存到本地名稱空間中,如果有sa子句,則使用as子句後的名稱
```python
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後面可以是包、模塊、類、函數、甚至是某個變量
2.2 自定義模塊
一個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
需要註意的是,自定義模塊的名稱要遵循一定的規範:
- 模塊名就是文件名。
- 模塊名必須符合標識符命名要求,是非數字開頭的字母數字下劃線的組合。
- 不要使用那個系統模塊名,否則會出現沖突覆蓋的問題。
- 模塊名通常全部為小寫,可以添加_下劃線。
2.3 模塊搜索順序
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是一個列表,那麽就意味著可以進行增刪改查,不過一般我們不會操作這個路徑,除非你真的需要。
2.4 模塊的重復導入
我們說模塊的導入就是一個加載以及關聯到標識符存入本地命名空間的過程,如果我要多次導入相同的模塊,是不是要多次加載,多次重新存入本地命名空間呢,寫個代碼測試一下
import os
print(id(os))
import os as daxin
print(id(daxin))
import os
print(id(os))
# 2300567923272
# 2300567923272
# 2300567923272
三次導入的模塊的內存地址是相同的,說明只會導入一次,這是為什麽呢?這是因為在Python中,所有的加載的模塊都會記錄在sys.modules中,它裏面存放的是已經加載過的所有模塊的字典,導入模塊就和它有關:
- import/from 觸發導入操作
- 解釋器在sys.modules中查找是否已經加載過,如果已經加載則直接把加載後的對象,及名稱加入到導入操作所在的命名空間中。
- 如果使用了as語句,則將對象關聯到新的標識符後,加入到導入操作所在的命名空間中。
- 如果sys.modules中不存在,則在sys.path中尋找該模塊,然後加載並寫入到sys.modules中,然後重復2.3這個過程。
- 如果沒找到,那麽就會報ImportError異常。
2.5 模塊的運行
????????每個模塊都會定義一個__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文件存在於同一層級下:
- 直接運行mymodule.py,會打印__main__
- 在myscript.py中導入時,會打印 mymodule
很多時候,我們寫的函數,類需要在其他地方調用,導入時會自動執行模塊,如果存在很多print語句時,就會直接在導入的地方進行輸出,不便於調用。所以一般我們都使用下面結構:
if __name__ == '__main__':
pass
他的好處是:
- 直接運行當前Python文件,才會__name__屬性為__main__,會執行後續代碼
- 使用import導入時,文件的__main__屬性被重置為文件名,後續代碼不會被執行,便於調用。
屬性 | 含義 |
---|---|
__file__ | 當前文件的絕對路徑(字符串格式) |
__cached__ | 編譯後的字節碼文件路徑(字符串格式) |
__sepc__ | 顯示模塊的規範 |
__name__ | 模塊名 |
__package__ | 當模塊是包時,同__name__,否則可以設置為頂級模塊的空字符串。 |
2.6 包
????????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調用了。
2.6.1 模塊和包的區別
- 包能更好的組織模塊,尤其是大量的模塊,其代碼行數很多,可以把它按照功能拆分成多個字模塊,使用哪個功能時,加載對應的子模塊即可。
- 包目錄中的__init__.py是在包第一次導入的時候就會執行,內容可以為空,也可以是用於該包初始個跨工作的代碼,最好不要刪除它。
- 包目錄之間只能使用點好作為分隔符,表示模塊的層級關系
- 模塊就是名稱空間,其內部的頂層標識符,都是它的屬性,可以通過__dict__或者dir(module)查看。
- 包是一個特殊的模塊,但模塊不一定是包,是一種組織模塊的方式,它包含__path__屬性
from json import encoder
json.dump可以使用嗎? # 無法使用,因為雖然加載了json模塊,但是僅僅把encoder導入了當前名稱空間
import json.encoder
json.dump可以使用嗎? # 可以使用,因為eocoder是一個模塊,這種導入當時,只會將頂層json模塊加入到本地名稱空間中,所以可以通過json執行dump函數。
2.7 相對導入與絕對導入
????????絕對導入:在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,是正常的。這是因為:一旦一個模塊中使用了相對導入,就不可以作為主模塊運行了
。
使用相對導入的模塊就是為了內部相互引用資源的,不是為了直接運行的,對於包來說,正確的使用方式還是在頂級模塊使用這些包。
2.8 訪問控制
????????模塊中的變量、類、函數等等都可以稱作模塊的屬性,在導入的時候,都可以被直接導入到本地命名,只要它們符合一個合法的標識符即可(包名,也同樣適用)
# 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']
也就是說模塊內沒有私有變量,在模塊定義中不做特殊處理(類的私有變量會被改名)
2.8.1 from xxx import *
使用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']
2.8.2 __all__
一般寫在文件的前幾行,用於表示當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 * 導入時:
- 如果模塊沒有__all__屬性,那麽只會導入非下劃線開頭的變量,如果是包,子模塊也不會導入,除非在__all__中設置,或者__init__.py中導入它們
- 如果模塊有__all__,from xxx import * ,只導入__all__類表中指定的名稱,哪怕這個名稱是下劃線開頭的,或者是子模塊
- 使用起來雖然很簡單,但是導入了大量不需要使用的變量,__all__就是控制被導入的變量的,為了避免沖突以及汙染名稱空間,建議添加__all__屬性
29 - 異常處理-模塊化