python加減乘除符號_Python專案如何合理組織規避import天坑
技術標籤:python加減乘除符號
本文共3500餘字,預計閱讀時間12分鐘,本文知乎連線:Python專案如何合理組織規避import天坑,本文同步釋出於silaoA的部落格和微信公眾號平。
關注學習瞭解更多的Cygwin、Linux、Python技術。
目 錄
- 0x00 module與package
- 程式碼複用性和分層思想
- module
- package
- 0x01 import
- 基本形式
- relative import和absolute import
- 0x02 專案組織建議
- 參考
- 更多閱讀
Python程式程式碼中經常需要import語句,呼叫已有的函式、方法等,但在多目錄、多檔案的專案程式中,容易遇到各種import錯誤,本文將通過合理化組織規避import帶來的錯誤。本文描述針對Python3,Python2已過時並將進入無人維護狀態,如是初學者建議直接選Python3。
0x00 module與package
程式碼複用性和分層思想
程式程式碼如果完成的功能比較簡單,放在一個檔案裡比較正常。如果隨著需求變多,要完成的功能越來越多樣化,那麼正常的做法都是按照功能模組做好設計,將程式程式碼分割成多個目錄、檔案組織,每個檔案完成各自的那部分功能即可。這樣做的好處,一方面是便於維護,一旦發現bug方便快速定位和除錯;另一方面是可以提高程式碼複用性,比如最基礎、最常用的那部分程式碼檔案,還可以被其他專案所用,不必從零開始重寫。
還沒見過哪種程式語言不支援多檔案程式設計(如果真有,那也該淘汰了),Python也不例外。
既然支援多檔案組織程式了,那麼如何組織
就是個大學問。在Cygwin系列(九):Cygwin學習路線文中也提到了計算機系統粗略層次劃分,在應用程式層面也可以照此繼續細分更多層。
照這個思路,可以簡單地把程式再分為驅動
和應用
層,驅動
負責實現基本操作、遮蔽細節,也可以叫底層
;應用
層根據驅動
提供的介面呼叫相關函式,實現更高階的功能,也可以叫上層
。比如Python中len
、sum
等函式,我們不用關心它是怎麼實現的,也不需要重寫求長度、求和的函式,自己寫的程式程式碼只要按照介面規定傳入引數即可得到預期結果,從而實現更高階的功能需求,那麼提供len
、sum
等函式的程式碼檔案就稱為底層
,我們自己寫的程式碼可稱為上層
。更進一步地,我們自己寫的程式碼可以劃分更多層次出來,不難理解,底層
與上層
是相對而言的。
把程式設計比作建大樓,層次可以劃分為 水泥沙子製作、磚頭製作、牆體制作、樓房製作 4層,樓房製作層直接取用牆體搭建房子,必要時可以取用磚頭,牆體制作層直接取用磚頭形成各式各樣的牆體,磚頭製作層直接取用水泥沙子形成不同規格的磚頭,而水泥沙子一般是有做好的。並且到了某次需要建大橋的時候,水泥沙子製作、磚頭製作這些功能可以再用,不必重新實現,減少了工作量。
module
在Python中,一個.py檔案就是一個module,即“模組”,模組的名稱是檔名去掉末尾的'.py'。一個模組(A)中的變數、函式、類等符號,被其他模組(B)import之後,可被其他模組(B)引用。我們寫程式程式碼的時候,就可以把程式碼分門別類地放在不同的.py檔案中,按照各自的層級位置,實現各自的功能。
比如將程式分成brick.py
、wall.py
、house.py
。 - brick.py
實現磚頭製作函式,不同的引數指定磚頭規格; - wall.py
中import brick
,呼叫函式得到不同規格磚頭,組裝形成牆體; - house.py
是主程式,import wall
後呼叫函式得到牆體,必要時也可以import brick
製作特定的磚頭,最後統一組裝成房子。
Python是“電池內建”型(battery included)語言,初次安裝Python直譯器時會一併安裝很多基礎的模組和包,稱為“標準庫(standard library)”,在Python安裝路徑的lib
子目錄下可看到。此外,使用者可以自行安裝其他第三方庫,預設會放在lib/site-packages
子目錄下。
有的模組出於執行效率考慮,是被編譯進Python直譯器的,並不以.py
檔案形式存在,比如常用的builtins
、sys
等。內建的len()
函式、加減乘除運算等就在builtins
模組中,使用時甚至不需要我們import,Python直譯器在啟動時便已import好了,而sys
模組需要先import才能使用。
package
自己寫的模組應該避免與Python內建模組重名,但不同人編寫的模組名稱相同怎麼辦?為解決名稱衝突問題,Python引入按照目錄組織模組的方法,創造了package(包)的概念。包是一個特殊的目錄,其下必須含有名為__init__.py
的檔案,否則Python會將其當作普通目錄而不是包,目錄下__init__.py
檔案對應的模組名就是包(目錄)名,檔案內容可以為空。
有了包,只要頂層包名稱與其他人不發生衝突,模組名稱便不會衝突。如下目錄組織,頂層包為soud
目錄,import成功後,相應模組名稱為sound.example
、sound.formats.wavread
、sound.effects.echo
、sound.filters.vocoder
等,要引用模組中的變數、函式符號,按照模組名.符號名
方式使用即可。
sound/ # 頂層包
__init__.py # 初始化sound包
formats/ # 子包formats
__init__.py
wavread.py
wavwrite.py
...
effects/ # 子包effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ # 子包filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
example.py
0x01 import
基本形式
import語句支援兩種形式:
- import xx
xx
是模組名(指含包路徑的模組全名)的話,通過xx.符號
的形式訪問函式、變數、類。xx
僅是包名的話,只會import到包下子模組,並不會自動import子包,可以通過xx.mod1.符號
的形式訪問函式、變數、類,通常會在包下的__init__.py
中寫入import子包語句,這樣便能通過xx.subpack1.mod2.符號
形式訪問mod2中的函式、變數、類,還有更多技巧進一步簡化訪問子包下模組內的符號。xx
不能定位到任何函式、變數、類名。
- from xx import yy
xx
是模組名(指含包路徑的模組全名)的話,yy
就定位到具體的函式、變數、類。xx
是包名的話,yy
可以定位到子包、模組或具體的函式、變數、類,比如from sound import __version__
,Python直譯器首先檢測__version__
是否為sound/__init__.py
中定義的符號,若不是,再檢測是否存在sound.__version__
模組,如不存在便報錯。如果yy
是*
,那麼代表引入xx
模組中定義的所有符號;對於包,可以在包下__init__.py
檔案定義__all__
變數規定允許匯出的所有符號或模組,如無__all__
定義,則該語句只負責importxx
包,執行包下__init__.py
檔案,當然檔案內可以import子模組或子包。from xx import *
形式容易造成命名衝突,建議慎用!
以上兩種形式,可以後續加as
表示式,對import的物件取別名,如import numpy as np
。
equalizer.py
、vocoder.py
檔案在同級目錄下,equalizer.py
中使用import vocoder
或者from vocoder import xx
,然後執行python sound/filters/equalizer.py
是不會報ImportError
錯誤的。那麼,不在同級目錄下該怎麼處理呢?
relative import和absolute import
簡單來說,相對匯入(relative import)的語句通過使用.
或..
等相對路徑的方式來定位要import的模組/包,基準路徑是當前模組所在包,如在sound/effects/reverse.py
中,基準路徑是sound.effects
包,使用下屬語句都是合法的。
from . import echo # 對應模組 ./echo.py
from .surround import XXX # 對應模組 ./surround.py內的XXX符號
from .. import formats # 對應包 ../formats
from ..filters import equalizer # 對應模組 ..filters/equalizer.py
from ... import example # 對應模組 ../../example.py
但在執行python sound/effects/reverse.py
時便會報ModuleNotFoundError
錯誤,這是因為執行語句將sound/effects/reverse.py
當做主模組(main module),主模組的名稱在執行時會被Python強制命名為__main__
,從而找不到__main__.echo
模組,規避的正確方式是使用-m
選項執行指定模組,即python -m sound.effects.reverse
執行正常。相對匯入方式可以解決匯入當前目錄、上級目錄、上上級目錄下模組的需求,但使用相對匯入的檔案不能直接以指令碼方式執行,只能以模組方式(-m選項)執行,並且當前路徑要處於import語句定位的路徑或更高層路徑。
絕對匯入(absolute import)與上相反,不使用.
或..
定位要import的模組/包的路徑,而使用完整路徑定位模組/包,Python根據sys.path
的值逐路徑搜尋要匯入的模組/包,而對於內建的和安裝第三方的模組/包,其路徑都在sys.path
值中。絕對匯入的方式無法匯入上級目錄的模組,雖然可以通過在程式中改寫sys.path
追加特定路徑的方式規避,但不推薦,會增加風險;使用絕對匯入的文件,應使用指令碼方式直接執行。
0x02 專案組織建議
為提高程式碼複用和有效模組化組織,建議使用主程式+包的方式組織專案,其中包內按分層思想放置若干模組,包內模組間使用相對匯入(relative import),專案根目錄下放置主程式檔案。仍以開頭建大樓為例,組織如下:
projectname/ # 專案目錄
#__init__.py # 也可以變成包,供今後其他專案import
construct/ # 子包construct用於建造過程
__init__.py
brick.py # 製作磚頭
wall.py # 製作牆體
misc.py # 其他不好歸類的功能
...
decoration/ # 子包decoration用於裝修裝飾過程
__init__.py
room.py # 放間裝修裝飾
clean.py # 清潔
...
doc/ # 專案文件目錄
buildhouse.py # 專案主程式,如果較複雜應繼續分拆
README.md # 專案簡介
- 在
construct
包下,對brick
模組都使用from .brick import (XX, YY, ZZ)
的形式,明確要import的變數、函式、類名稱; - 同時,在
__init__.py
中使用from .brick import (XX, YY, ZZ)
、from .wall import (AA, BB, CC)
等把這些可以對外匯出的符號import一遍,這樣construct
包被construct
包下模組或主程式buildhouse.py
匯入時,可以簡化為from ..construct import (XX, CC)
、from construct import (AA, BB, CC, XX, YY, ZZ)
或import construct
。 - 儘可能地減少在import語句使用 "*" 符號,實在無法避免時,為限制 "*" 符號帶來的命名衝突,在
construct/__init__.py
、decoration/__init__.py
檔案中,定義__all__
變數,明確限定允許被匯出的所有符號。 - 主程式執行時,先切換到
projectname
目錄下,再執行python buildhouse.py
,或者直接python path/to/projectname/buildhouse.py
若專案進一步複雜,一方面可能增加更多的包,另一方面在包目錄之下可以增加子包,使層次更加清晰明瞭。
參考
- Python的import陷阱
- Python 3.6.7 Documentation - The Python Tutorial.
更多閱讀
- Cygwin系列(九):Cygwin學習路線
- 微軟WSL——Linux桌面版未來之光
- 專欄:偽碼人We_Coder
- GNU Wget 爬蟲?試一試
- silaoA的部落格.https://silaoa.github.io
如本文對你有幫助,或內容引起極度舒適,歡迎分享轉發與留言交流
►本文為原創文章,如需轉載請私信知乎賬號silaoA或聯絡公眾號偽碼人(We_Coder)。
都看這裡了,不妨點個贊再走唄