1. 程式人生 > 其它 >python加減乘除符號_Python專案如何合理組織規避import天坑

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學習路線文中也提到了計算機系統粗略層次劃分,在應用程式層面也可以照此繼續細分更多層。

b29387ad84039fa797ed1c50e3875402.png
計算機系統粗略層次劃分

照這個思路,可以簡單地把程式再分為驅動應用層,驅動負責實現基本操作、遮蔽細節,也可以叫底層應用層根據驅動提供的介面呼叫相關函式,實現更高階的功能,也可以叫上層。比如Python中lensum等函式,我們不用關心它是怎麼實現的,也不需要重寫求長度、求和的函式,自己寫的程式程式碼只要按照介面規定傳入引數即可得到預期結果,從而實現更高階的功能需求,那麼提供lensum等函式的程式碼檔案就稱為底層,我們自己寫的程式碼可稱為上層。更進一步地,我們自己寫的程式碼可以劃分更多層次出來,不難理解,底層上層是相對而言的。

把程式設計比作建大樓,層次可以劃分為 水泥沙子製作、磚頭製作、牆體制作、樓房製作 4層,樓房製作層直接取用牆體搭建房子,必要時可以取用磚頭,牆體制作層直接取用磚頭形成各式各樣的牆體,磚頭製作層直接取用水泥沙子形成不同規格的磚頭,而水泥沙子一般是有做好的。並且到了某次需要建大橋的時候,水泥沙子製作、磚頭製作這些功能可以再用,不必重新實現,減少了工作量。

module

在Python中,一個.py檔案就是一個module,即“模組”,模組的名稱是檔名去掉末尾的'.py'。一個模組(A)中的變數、函式、類等符號,被其他模組(B)import之後,可被其他模組(B)引用。我們寫程式程式碼的時候,就可以把程式碼分門別類地放在不同的.py檔案中,按照各自的層級位置,實現各自的功能。

比如將程式分成brick.pywall.pyhouse.py。 - brick.py實現磚頭製作函式,不同的引數指定磚頭規格; - wall.pyimport brick,呼叫函式得到不同規格磚頭,組裝形成牆體; - house.py是主程式,import wall後呼叫函式得到牆體,必要時也可以import brick製作特定的磚頭,最後統一組裝成房子。

Python是“電池內建”型(battery included)語言,初次安裝Python直譯器時會一併安裝很多基礎的模組和包,稱為“標準庫(standard library)”,在Python安裝路徑的lib子目錄下可看到。此外,使用者可以自行安裝其他第三方庫,預設會放在lib/site-packages子目錄下。

有的模組出於執行效率考慮,是被編譯進Python直譯器的,並不以.py檔案形式存在,比如常用的builtinssys等。內建的len()函式、加減乘除運算等就在builtins模組中,使用時甚至不需要我們import,Python直譯器在啟動時便已import好了,而sys模組需要先import才能使用。

package

自己寫的模組應該避免與Python內建模組重名,但不同人編寫的模組名稱相同怎麼辦?為解決名稱衝突問題,Python引入按照目錄組織模組的方法,創造了package(包)的概念。包是一個特殊的目錄,其下必須含有名為__init__.py的檔案,否則Python會將其當作普通目錄而不是包,目錄下__init__.py檔案對應的模組名就是包(目錄)名,檔案內容可以為空

有了包,只要頂層包名稱與其他人不發生衝突,模組名稱便不會衝突。如下目錄組織,頂層包為soud目錄,import成功後,相應模組名稱為sound.examplesound.formats.wavreadsound.effects.echosound.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.pyvocoder.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__.pydecoration/__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

如本文對你有幫助,或內容引起極度舒適,歡迎分享轉發與留言交流

97089768905d718d3007a780f42fcf58.png

83c51b12ec3aa935e6736cd6a03c24d3.png

►本文為原創文章,如需轉載請私信知乎賬號silaoA或聯絡公眾號偽碼人(We_Coder)。

都看這裡了,不妨點個贊再走唄