29 - 異常處理-模塊化

1 異常

        在程序運行過程當中,總會遇到各類各樣的錯誤。有的錯誤是程序編寫有問題形成的,好比原本應該輸出整數結果輸出了字符串,有的錯誤是用戶輸入形成的,好比讓用戶輸入email地址,結果獲得一個空字符串,這種錯誤能夠經過檢查用戶輸入來作相應的處理。還有一類錯誤是徹底沒法在程序運行過程當中預測的,好比寫入文件的時候,磁盤滿了,寫不進去了,或者從網絡抓取數據,網絡忽然斷掉了。這類錯誤也稱爲異常,在程序中一般是必須處理的,不然,程序會由於各類問題終止並退出。linux

1.1 產生異常

在Python中產生異常主要有兩種方式:解釋器出觸發和手動觸發編程

當異常被觸發後,觸發異常的代碼段的後續代碼將不會被繼續執行。若是存在於頂層命名空間中,可能還會致使整個程序退出。json

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 

    ......

不傳遞參數時,使用默認屬性初始化,傳遞參數時,則參數進行實例初始化賦值。ui

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的工做原理:

  1. 若是try中語句執行時發生異常,搜索except子句,並執行第一個匹配該異常的except子句
  2. 若是沒有匹配的except子句,異常將被傳遞到外層的try(若是有),若是外層不處理這個異常,將繼續向外拋出,到達最外層,尚未被處理,就終止異常所在的線程
  3. 若是在try執行時沒有發生異常,將執行else子句中的代碼段
  4. 不管try中是否發生異常,finally子句都將會被執行

2 模塊化

通常來講,編程語言中,庫,包,模塊是同一種概念、是代碼的組織方式。Python中只有一種模塊對象類型,可是爲了模塊化組織模塊的便利,提供了'包'的概念。

  • 模塊module:指的是一個個Python的源代碼文件
  • 包package:指的是模塊組織在一塊兒的包名同名的目錄及其相關文件

2.1 導入語句

Pythong主要提供了兩類導入模塊的方法。它們是import和from ... import。

2.1.1 import導入

語句 含義
import n1,n2 徹底導入
import x as x 模塊別名徹底導入

導入過程:

  1. 找到指定的模塊,加載和初始化它,生成模塊對象。找不到,拋出異常
  2. 在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 別名部分導入

導入過程:

  1. 找到from子句中指定的模塊,加載並初始化它們(不是導入)
  2. 對於import 子句後的名稱
    1. 先查from子句加載的模塊是否具備該名稱的屬性
    2. 若是不是,則嘗試導入該名稱的子模塊
    3. 尚未找到,則拋出ImportError異常
    4. 這個名稱保存到本地名稱空間中,若是有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

須要注意的是,自定義模塊的名稱要遵循必定的規範:

  1. 模塊名就是文件名。
  2. 模塊名必須符合標識符命名要求,是非數字開頭的字母數字下劃線的組合。
  3. 不要使用那個系統模塊名,不然會出現衝突覆蓋的問題。
  4. 模塊名一般所有爲小寫,能夠添加_下劃線。

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模塊的路徑搜索順序,當加載一個模塊的時候,須要從這些路徑中從前到後依次查找,但並不搜索這些目錄的子目錄,搜索到模塊就加載,搜索不到就拋異常。

大體的路徑順序爲:

  1. 程序主目錄,運行程序的主程序所在的目錄
  2. PYTHONPATH目錄,環境變量PYTHONPATH設置的目錄,也是搜索模塊哦路徑
  3. 標準庫目錄,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中,它裏面存放的是已經加載過的全部模塊的字典,導入模塊就和它有關:

  1. import/from 觸發導入操做
  2. 解釋器在sys.modules中查找是否已經加載過,若是已經加載則直接把加載後的對象,及名稱加入到導入操做所在的命名空間中。
  3. 若是使用了as語句,則將對象關聯到新的標識符後,加入到導入操做所在的命名空間中。
  4. 若是sys.modules中不存在,則在sys.path中尋找該模塊,而後加載並寫入到sys.modules中,而後重複2.3這個過程。
  5. 若是沒找到,那麼就會報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文件存在於同一層級下:

  1. 直接運行mymodule.py,會打印__main__
  2. 在myscript.py中導入時,會打印 mymodule

不少時候,咱們寫的函數,類須要在其餘地方調用,導入時會自動執行模塊,若是存在不少print語句時,就會直接在導入的地方進行輸出,不便於調用。因此通常咱們都使用下面結構:

if __name__ == '__main__':
    pass

他的好處是:

  1. 直接運行當前Python文件,纔會__name__屬性爲__main__,會執行後續代碼
  2. 使用import導入時,文件的__main__屬性被重置爲文件名,後續代碼不會被執行,便於調用。
屬性 含義
__file__ 當前文件的絕對路徑(字符串格式)
__cached__ 編譯後的字節碼文件路徑(字符串格式)
__sepc__ 顯示模塊的規範
__name__ 模塊名
__package__ 當模塊是包時,同__name__,不然能夠設置爲頂級模塊的空字符串。

2.6 包

        Python中把包理解爲一個特殊的模塊,在文件系統上的體現就是一個目錄。用於組織多個模塊或者其餘更多的包。主要有兩種格式:

  1. 目錄
  2. 目錄下包含一個__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 模塊和包的區別

  1. 包能更好的組織模塊,尤爲是大量的模塊,其代碼行數不少,能夠把它按照功能拆分紅多個字模塊,使用哪一個功能時,加載對應的子模塊便可。
  2. 包目錄中的__init__.py是在包第一次導入的時候就會執行,內容能夠爲空,也能夠是用於該包初始個跨工做的代碼,最好不要刪除它。
  3. 包目錄之間只能使用點好做爲分隔符,表示模塊的層級關係
  4. 模塊就是名稱空間,其內部的頂層標識符,都是它的屬性,能夠經過__dict__或者dir(module)查看。
  5. 包是一個特殊的模塊,但模塊不必定是包,是一種組織模塊的方式,它包含__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 * 導入時:

  1. 若是模塊沒有__all__屬性,那麼只會導入非下劃線開頭的變量,若是是包,子模塊也不會導入,除非在__all__中設置,或者__init__.py中導入它們
  2. 若是模塊有__all__,from xxx import * ,只導入__all__類表中指定的名稱,哪怕這個名稱是下劃線開頭的,或者是子模塊
  3. 使用起來雖然很簡單,可是導入了大量不須要使用的變量,__all__就是控制被導入的變量的,爲了不衝突以及污染名稱空間,建議添加__all__屬性
相關文章
相關標籤/搜索