看板 Python 關於我們 聯絡資訊
原諒我只回關於import的部份XD。這部份我發現第一次遇到很難搞懂又常常忘記,我自己 也是邊查邊寫。 import 的邏輯是什麼!? ====================== 這邊比較能夠細讀的官方文件是PEP-328和 https://docs.python.org/3/tutorial/modules.html 。 ## Absolute Import Absolute import 非常簡單,只要不是以點(dot)開頭的就是absolute import,例如: import foo import foo.bar as bar from foo import bar Absolute import的邏輯就是先找built-in module,沒有的話從`sys.path`的資料夾底下 找一個<name>.py的檔案或是<name>的package(package的定義:帶有`__init__.py`)。 `sys.path`的組成包含 * 程式進入的script的資料夾 * `PYTHONPATH`環境變數 * 安裝python時指定的位置,通常是放安裝的檔案 ## Relative Import Relative import 以點作為起始,兩個點代表上一層,以此類推,而且永遠都是用 `from <> import <>` 來敘述。 至於這個"relative"是相對什麼?這個是用當前module的`__name__`來決定 * 如果它是`foo.bar`之類的值,那就是從`foo.bar`來做相對移動。 `foo`就是最上層的module。 * 如果它是`__main__`(當你執行python script的進入點), 那這個script就是最上層的module。 最後,不管是那一種relative import,都不能超過最上層的module。 ## Import, the Pythonic Way 首先是,不要使用`from foo import *`這種形式。python的名字是會被覆蓋的,也就是 說如果連續兩行 `from <name> import *`然後又有重複的名字,那就會被第二行覆蓋。 然後,分開程式進入點的檔案(`__name__`是`__main__`)還有package。理由是你的進 入點會變成是`__main__`,那就沒辦法用relative import,那你就只能仰賴`sys.path` 來找你自己開發的檔案。如果你不想要自己加工`sys.path`,那就要把你的package放在 程式進入點那層,也就是這樣 project/ main.py setup.py packageA/ ... tests/ funtionA/ test_XXX.py ... ... 最後,盡量不要加工`sys.path`。原因跟第一點一樣是名稱衝突的問題:module是依序在 `sys.path`裡面找,加工會影響其他module,可能會讓後來的module import到錯的檔案, 這種超級難debug。 ## Take Away * absolute import: 用`sys.path`決定 * relative import: 用`__name__`決定,不能超過top-level module * 不要用`*`、分開entrypoint和package、不要加工`sys.path` -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 114.44.244.29 ※ 文章網址: https://www.ptt.cc/bbs/Python/M.1529654464.A.920.html
uranusjr: Absolute import 那邊不太對, 它只看 sys.path, 不一定 06/22 19:25
uranusjr: 是先找 built-in (其實 Python 直譯器本身並不知道哪些 06/22 19:26
uranusjr: 模組是 built-in 哪些不是) 06/22 19:26
uranusjr: 不過這篇的概念很棒, 可惜我不是板主不能 m... 06/22 19:28
ThxThx: 嗯!?那部份我是從https://tinyurl.com/qaasj5n 翻譯來 06/22 23:31
ThxThx: 我猜這裡的built in是指用C寫的那些modules,不過從來沒試 06/22 23:31
ThxThx: 過XD。 06/22 23:31
os653: 想請教一下,那 debug 跟 test 的檔案要放哪裡? 06/23 08:45
os653: 按照此篇所述,debug 跟 test 的檔案要放在 project 最上層 06/23 08:45
os653: 如此才能不加工 sys.path 又能使用 relative import 06/23 08:46
sys.path和relative import無關
os653: 但這很奇怪呀,尤其 test 還有可能依測試目的放不同資料夾 06/23 08:47
我修改了上面project的架構,test資料夾通常會放在top-level。 我假設你說的debug是指開發的時候用來測的script,test是指拿來跑unit test的檔案。 Debug我會把他看成是用完就丟不會把他放進版控,所以不是放在最上層就是加`sys.path` ,不然就是用`PYTHONPATH` test的話有蠻多種的,我是用pytest這個framework,然後執行的時候加`PYTHONPATH` ,像是 PYTHONPATH='./' pytest 另外,如果你寫好setup.py,那可以用tox這個工具幫你創好虛擬環境(而且一次跑多個 版本的python),安裝你寫的package然後跑你要的指令。所以open source常見的組 合就是 tox(創環境) + pytest(找test script來跑) + travis(免洗的標準環境) 當然小project可以不用這麼認真,原則就是自動化test,source code不要亂加東西。 最後請注意,以上講的都是用absolute import。Relative import是在你寫的package裡面 用,absolute import是用來找到package的root folder。 ※ 編輯: ThxThx (114.44.244.29), 06/23/2018 11:39:54
os653: 感謝解說,有空試試看這篇裡講的東西看看會不會比較好用 06/23 15:41
clsmbstu: 感謝解說!有幾個問題我想再請教一下: 06/24 14:30
clsmbstu: 1. PYTHONPATH環境變數可以賦予數個不同的資料夾嗎? 06/24 14:31
就跟你設PATH一樣用冒號隔開
clsmbstu: 2. 如果我沒理解錯,看起來relative import只能寫在「會 06/24 14:33
clsmbstu: 被其它script/module import的module」?在程式本身寫 06/24 14:34
clsmbstu: relative import的話,一定會因為碰到__main__而錯誤。 06/24 14:34
不像javascript,code愛放哪就放哪 import python module要先找到package(用absolute import)然後再用點(dot)往下鑽 relative import是給你在自己的package底下互相引用用的
clsmbstu: BTW 對我來說 最常碰到的困擾倒不是因為debug或test檔案 06/24 14:41
clsmbstu: 我在做的大多是data analyses,所以本來就會把檔案歸類 06/24 14:45
clsmbstu: 成不同的資料夾,例如: 06/24 14:45
clsmbstu: projects/crons/month 是產生資料月報有關的檔案 06/24 14:47
clsmbstu: projects/crons/week 是產生資料週報有關的檔案 06/24 14:47
clsmbstu: projects/prediction_model 跟公司業務預測模型有關 06/24 14:48
clsmbstu: projects/connection 連接公司資料庫所需的package 06/24 14:49
clsmbstu: projects/utils 是資料前處理、資料清洗的package 06/24 14:50
clsmbstu: 這種狀況下,relative import卻完全幫不上忙 @@ 06/24 14:51
clsmbstu: 又要不修改sys.path的話,只能讓PYTHONPATH指向多處? 06/24 14:52
設PYTHONPATH是在執行的時候修改sys.path啊:P 如果是這樣,那還不如加在sys.path裡面,至少不用記得執行的時候要加東西。 你會這樣放code可能也有其他的考量,我會這樣建議是覺得,隨意加sys.path也許沒有增 加現在的開發成本,但是絕對是增加維護的成本。哪個成本比較重要就要看你的選擇了。 回過頭來,現在假設問題就是crons/week和prediction_model裡的scripts都需要utils的 package。以前可能是每個script都寫很長,但現在如果變成 projects/ crons_week.py crons_month.py (scripts) predict.py ... crons/ ... (產生資料報表的功能) prediction_model/ ... (model本身) utils/ ... (其他共用的邏輯) 所有第一層scripts的功能是parse CLI arguments然後呼叫其他資料夾裡面的功能,不要 把複雜的邏輯寫在這裡面。也許這樣每個人就很容易了解這些檔案在做什麼、每個package 各自擁有什麼功能。 所以就是,我主張每個package把他看成是功能的集合/公約數。不一定要照做,不過 至少你現在知道absolute/relative import的原理了XD
clsmbstu: 這也是為什麼我第一個問題那麼問 06/24 14:53
※ 編輯: ThxThx (114.44.244.29), 06/25/2018 01:27:26
clsmbstu: Thank you!!! 06/25 10:29