阿掖山
阿掖山

智力活动是一种生活态度 https://mountaye.github.io/blog/

.py | import 引用現成的代碼

正常的編程語言教程,教人配置完開發環境之後就應該進入正題,開始講語法了。但是咱不正常,所以先來談談怎麼用別人已經寫好的代碼。

以官網給出的文件結構為例來說明:

sound/ Top-level package
      __init__.py Initialize the sound package
      formats/ Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/ Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/ Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

使用現成的python 代碼

正常的編程語言教程,教人配置完開發環境之後就應該進入正題,開始講語法了。但是咱不正常,所以先來談談怎麼用別人已經寫好的代碼。其中最簡單的,就是可以直接通過包管理程序安裝的:

pip install sound

然後想要使用某個文件中的函數,比如假裝wavwrite.py中有個函數叫write() ,以下寫法都是可以的,注意不同import 方法對應不同的函數調用寫法:

import sound
sound.formats.wavwrite.write()

from sound import formats
formats.wavwrite.write() 

from sound.formats import wavwrite
wavwrite.write()

from sound.formats.wavwrite import write
write()

但是,不是所有的python 代碼都可以直接安裝,比如一篇論文的研究成果發表之後,處理數據的代碼也往往開源,但是這些作者基本上就只是把自己寫代碼的文件夾公開出來而已,我們把文件夾下載下來,然後直接import sound , 會報錯,提示找不到名為sound 的庫。

python 如何讀取代碼文件

仔細想想,找不到才是正常的,之前輕輕鬆鬆的一句import sound就解決問題,這才不簡單——不同的庫往往位於文件系統的不同位置,但我們只要寫出他們的名字就行了,不需要指定文件路徑。電腦硬盤那麼大,找到庫卻幾乎是瞬間完成的。

這是因為python 並沒有搜索整個硬盤。有一個變量,一般名為PYTHONPATH ,其變量值是一個列表,表中成員是含有python 庫文件夾的路徑。當我們在命令行輸入命令的時候,電腦會:

  • 搜索當前所在的文件夾,也就是在命令行輸入python時終端所在的文件夾。
  • 遍歷PYTHONPATH中的文件夾。
  • python 包管理程序默認的位置,一般是<path to python>/site-package

看看有沒有我們要引用的庫,找到了就引入,找不到就報錯。

上一節的錯誤中,如果我們恰好位於sound 所在的文件夾,然後運行python,此時第一條生效, import sound不會報錯,但在其他位置就不行了。

名詞解釋:interactive, script, module, package

可執行的python 命令可以出現在以下四個地方,第一種是接受鍵盤輸入的程序,後三種都是文件:

  1. interactive: python交互式界面,也叫做calculator mode,也就是在命令行輸入python之後出現的界面。每次輸入一句,結果在命令行上顯示出來。當python 退出之後,輸入過的命令就消失了。
  2. script: python腳本文件,也就是在命令行輸入python somefile.py裡面的那個somefile.py
  3. 畢竟python 是一種很輕量化的語言,在一定程度上可以起到shell 的作用,有些命令我們並不想要用完就扔,而是保存起來以便以後重複執行,另外很多命令的組合組合成函數也可以極大地簡化工作。在這種語境之下, interactive 和script 的關係,就好像Linux 命令行和bash script 的關係一樣。
  4. 但同時python 又是一種功能很全面的語言,完全可以勝任複雜的面向對象編程。在這種語境之下,script 也可以用來指代main module,也就是程序執行的主文件和入口,和下面的一般的module 相區分。
  5. module: python模塊文件,也就是在命令行輸入python -m another裡面的那個another (注意這裡不寫拓展名.py )。按照官方文檔的說法,所有.py文件都是module。但是實際上這句話很有誤導性,上一節的main module 和一般的module 非常不同,下一節會詳細展開講。一般提到module,都是在強調這個文件定義的變量和函數可以被其他的python 文件引用。
  6. package: python,互相關聯的modules 構成的更高一級的可供引用的結構,簡單理解就是含有__init__.py的文件夾,但是python 並不是根據文件夾和文件之間的從屬關係來確定package 和module 之間的關係的,下一節會詳細展開講。

script vs. module

python 同時兼具腳本語言的靈活性,和各種重型語言的功能全面性。因為前者,所以它並不要求程序作者一定要在一個叫做main.py的文件裡寫一個名叫Main的類, 然後在裡面實現一個main()方法。但是因為後者,沒寫不代表python 不需要知道一個複雜程序執行的起點。

這個起點就是不帶有-m參數的python命令後面跟著的.py文件,這就使得這個文件變得比其他.py文件特殊。底層表現就是python 會不管這個文件的名字叫什麼,都將它的__name__屬性賦值為"__main__" 。這樣,即便這個文件可能是一個大型庫中間的一個模塊,運行的時候python 連它的真名都不知道,就更找不到它同級和上下級的其他模塊了。

各種普通模塊被python 用到的方法就是通過在主模塊main module(或者說script)中import。經過“python 如何讀取代碼文件”一節中的搜索過程之後找到了所需模塊或包,模塊的名字、模塊之間的關係、模塊裡定義了哪些屬性和函數,就被python 了解了,從而當主模塊召喚他們的時候就知道去哪裡找相應的代碼。除了在被import 的時候, python -m命令的賓語也可以告訴python 被運行的模塊和包的相對關係: python -m sound.formats.wavwrite ,此時python 執行了wavwrite.py中的所有可執行的命令,同時知道從sound/wavwrite.py的各個包之間的關係。

absolute import vs. relative import

開頭使用已經安裝過的包使用的語法全都是絕對引用(absolute import),表現就是import 語句裡面沒有以.作為開頭的。

另外一種import 方法叫相對引用(relative import), .表示模塊所在的文件夾, ..表示模塊的上一級文件夾。主要用在各種明確知道自己是工具代碼,而且是一個更高層次結構的組成部分,幾乎永遠不需要被作為主模塊運行的代碼。

回到開頭例子裡的文件結構,假如sound/effects/surround.py 中想要使用sound/formats/wavwrite.py 和sound/effects/echo.py 中的函數,可以寫成:

# in sound/effects/surround.py
from ..formats import wavwrite
from . import echo

如何組織代碼,以便自己重用

研究終於推進到了準備寫論文的階段了(學渣本質暴露了),寫草稿之餘,之前幾年時間裡做過的處理和分析,接下來的一兩個月裡需要把工作流程規範化之後迅速重做一遍確認。

隨手寫散落各處的分析代碼需要整理到一起,之前試圖統一到一個項目之下,結果總是在某個模塊引用其他模塊的時候遇到報錯。於是才有了這篇文章。

以下是這篇文章給出的一個推薦的項目文件結構:

|- notebooks/
   |- 01-first-logical-notebook.ipynb
   |- 02-second-logical-notebook.ipynb
   |- prototype-notebook.ipynb
   |- archive/
	  |- no-longer-useful.ipynb
|- projectname/
   |- projectname/
	  |- __init__.py
	  |- config.py
	  |- data.py
	  |- utils.py
   |- setup.py
|- README.md
|- data/
   |- raw/
   |- processed/
   |- cleaned/
|- scripts/
   |- script1.py
   |- script2.py
   |- archive/
      |- no-longer-useful.py
|- environment.yml

學過這篇筆記包含的內容,我才理解作者這樣的安排。既然主文件很難沒辦法通過相對引用來找到工具代碼,索性就把工具代碼寫成一個完整可安裝的庫,然後就像numpy , pandas一樣在獨立的notebook 和scripts 中引用。

但是要讓一個包可安裝,需要創建並編輯setup.py這個文件。這篇文章已經夠長了,所以這個話題還是下次再說吧。

參考鏈接





CC BY-NC-ND 2.0 版權聲明

喜歡我的文章嗎?
別忘了給點支持與讚賞,讓我知道創作的路上有你陪伴。

載入中…
載入中…

發布評論