簡體   English   中英

開發 Python package 時導入的最佳實踐是什么?

[英]What is the best practice for imports when developing a Python package?

我正在嘗試構建一個 Python package,其中包含子模塊和子包(“庫”)。 我到處都在尋找正確的方法,但令人驚訝的是我發現它非常復雜。 當然也經歷了 StackOverFlow 中的多個線程..

問題如下:

  1. 為了從另一個目錄導入模塊或 package,在我看來有 2 個選項:a。 將絕對路徑添加到sys.path b. 使用setuptools.setup function 在 package 的主目錄中的setup.py文件中安裝 package - 將 package 安裝到正在使用的特定 Python 版本的site-packages目錄中。

  2. 選項a對我來說似乎太笨拙了。 選項b很棒,但我發現它不切實際,因為我目前正在工作和編輯包的源代碼 - 當然,更改不會在 package 的安裝目錄上更新。 另外package的安裝目錄沒有被Git跟蹤,不用說我用的是Git原目錄。

總結這個問題:從當前正在建設中的 Python package 的子目錄中自由且良好地導入模塊和子包的最佳實踐是什么?

我覺得我遺漏了一些東西,但到目前為止找不到合適的解決方案。

謝謝!

這是一個很好的問題,我希望更多的人能按照這些思路思考。 在其他人可以輕松使用之前,絕對有必要使模塊可導入並最終可安裝。

在 sys.path 修改上

在我回答之前,我會說當我對現有 package 結構之外的文件進行初始開發時,我確實使用了 sys.path munging。 我有一個編輯器片段可以構造如下代碼:

import sys, os
sys.path.append(os.path.expanduser('~/path/to/parent'))
from module_of_interest import *  # NOQA

給定我使用的當前文件的路徑:

import ubelt as ub
fpath = ub.Path('/home/username/path/to/parent/module_of_interest.py')
modpath, modname = ub.split_modpath(fpath, check=False)
modpath = ub.Path(modpath).shrinkuser()  # abstract home directory

為了構建必要的部分,代碼片段將插入到文件中,這樣我就可以在 IPython 中與它進行交互。 我發現花了一些額外的時間來刪除對我的顯式主文件夾的引用,這樣只要用戶具有與主目錄相同的相對路徑結構,代碼仍然可以工作,這使得它稍微更便攜。

妥善管理 Python Package

話雖這么說, sys.path munging 不是一個可持續的解決方案。 最終,您希望您的 package 由 python package 經理管理。 我知道很多人使用詩歌,但我喜歡普通的舊 pip,所以我可以描述這個過程,但知道這不是唯一的方法。

為此,我們需要了解一些基礎知識 go。

基本

  1. 你必須知道你在什么 Python 環境中工作。理想情況下,這是一個用pyenv (或conda或 mamba 或 poetry ...)管理的虛擬環境。 但也可以在您的全局系統 Python 環境中執行此操作,但不建議這樣做。 我喜歡在 my.bashrc 中始終激活的單一默認 Python 虛擬環境中工作。 它總是很容易切換到一個新的或吹走它/重新開始。

  2. 您需要考慮兩個根路徑:您的存儲庫的根目錄,我將其稱為您的 repo 路徑,以及您的根目錄到您的 package,package 路徑或模塊路徑,它應該是一個名稱為頂級 Python 的文件夾package。您將使用此名稱導入它。 此 package 路徑必須位於回購路徑內。 一些 repos,比如xdoctest ,喜歡將模塊路徑放在src目錄中。 其他人,如ubelt ,喜歡在存儲庫的頂層擁有 repo 路徑。 我認為第二種情況對於新的 package 創建者/維護者來說在概念上更容易,所以讓我們 go 吧。

設置回購路徑

所以現在,你處於一個激活的 Python 虛擬環境中,我們已經指定了一個路徑,我們將檢查 repo。 我喜歡在$HOME/code中克隆 repos,所以也許 repo 路徑是$HOME/code/my_project

在此回購路徑中,您應該有根路徑 package。 假設您的 package 名為 mypymod。 任何包含__init__.py文件的目錄在概念上都是一個 python 模塊,其中__init__.py的內容是您導入該目錄名稱時獲得的內容。 目錄模塊和普通文件模塊之間的唯一區別是目錄模塊/包可以有子模塊或子包。

例如,如果你在my_project mypymod ls ,你有一個看起來像這樣的文件結構......

+ my_project
    + mypymod
        + __init__.py
        + submod1.py
        + subpkg
            + __init__.py
            + submod2.py

您可以導入以下模塊:

import mypymod
import mypymod.submod1
import mypymod.subpkg
import mypymod.subpkg.submod2

如果您確保當前工作目錄始終是存儲庫根目錄,或者將存儲庫根目錄放入sys.path ,那么這就是您所需要的。 sys.path或 CWD 中可見是另一個模塊可以看到您的模塊所需要的。

package 清單:setup.py / pyproject.toml

現在的訣竅是:你如何確保你的其他包/腳本總能看到這個模塊? 這就是 package 經理的用武之地。為此,我們需要一個setup.py或更新的pyproject.toml變體。 我將描述舊的setup.py做事方式。

您需要做的就是將setup.py放在您的repo root中。 注意:它不在您的 package 目錄中的 go。 有很多關於如何編寫 setup.py的資源,所以我不會詳細描述它,但基本上您需要的只是用足夠的信息填充它,以便它知道 package 的名稱、它的位置和它的版本.

from setuptools import setup
setup(
    name='mypymod',
    version='0.1.0',
    packages=find_packages(include=['mypymod', 'mypymod.*']),
    install_requires=[],
)

所以你的 package 結構將如下所示:

+ my_project
    + setup.py
    + mypymod
        + __init__.py
        + submod1.py
        + subpkg
            + __init__.py
            + submod2.py

您可以指定很多其他內容,我建議您查看 ubelt 和 xdoctest 作為示例。 我會注意到它們包含從requirements.txtrequirements/*.txt文件中解析需求的非標准方式,我認為這通常比人們處理需求的標准方式要好。 但我離題了。

給定pip或其他一些 package 管理器(例如 pipx、poetry)識別為package 清單的內容 - 一個描述 package 內容的文件,您現在可以安裝它。 如果您仍在開發它,您可以在可編輯模式下安裝它,而不是將 package 復制到您的站點包中,只創建一個符號鏈接,因此每次調用 Python(或立即調用)時,代碼中的任何更改都會反映出來如果你使用 IPython 自動重新加載)。

使用 pip 就像運行pip install -e <path-to-repo-root>一樣簡單,這通常通過導航到 repo 並運行pip install -e. .

恭喜,您現在有一個 package 可以從任何地方參考。

充分利用您的 package

python -m 調用

現在你有了一個 package,你可以引用它,就好像它是通過 pip 從 pypi 安裝的一樣。 有一些技巧可以有效地使用它。 第一個是運行腳本。

在 Python 中,您無需指定文件路徑即可將其作為腳本運行。可以僅使用其模塊名稱將腳本作為__main__運行。 這是通過 Python 的-m參數完成的。例如,您可以運行python -m mypymod.submod1它將調用$HOME/code/my_project/mypymod/submod1.py作為主模塊(即它的__name__屬性將被設置為"__main__" )。

此外,如果您想使用目錄模塊執行此操作,您可以在該目錄中創建一個名為__main__.py的特殊文件,這就是將要執行的腳本。 例如,如果我們修改我們的 package 結構

+ my_project
    + setup.py
    + mypymod
        + __init__.py
        + __main__.py
        + submod1.py
        + subpkg
            + __init__.py
            + __main__.py
            + submod2.py

現在python -m mypymod將執行$HOME/code/my_project/mypymod/__main__.pypython -m mypymod.subpkg將執行$HOME/code/my_project/mypymod/subpkg/__main__.py 這是使模塊兼作可導入 package 和命令行可執行文件(例如 xdoctest 執行此操作)的一種非常方便的方法。

進口更容易

您可能會注意到的一個痛點是,在上面的代碼中,如果您運行:

import mypymod
mypymod.submod1

你會得到一個錯誤,因為默認情況下 package 在導入之前不知道它的子模塊。 您需要填充__init__.py以公開您希望在頂層訪問的任何屬性。 您可以使用以下內容填充mypymod/__init__.py

from mypymod import submod1

現在上面的代碼可以工作了。

不過這有一個折衷。 您立即訪問的東西越多,導入模塊所需的時間就越多,而且對於大包來說,它會變得相當麻煩。 此外,您還必須手動編寫代碼來公開您想要的內容,所以如果您想要一切,那將是一件痛苦的事情。

如果你看一下ubelt 的init .py ,你會發現它有大量代碼明確地使每個子模塊中的每個 function 都可以在頂層訪問。 我已經編寫了另一個名為mkinit的庫,它實際上自動執行了這個過程,它還可以選擇使用lazy_loader庫來減輕在頂層公開所有屬性對性能的影響。 我發現 mkinit 工具在編寫大型嵌套包時非常有用。

概括

總結以上內容:

  1. 確保你在 Python virtualenv 中工作(我推薦 pyenv)
  2. 在您的“回購路徑”中識別您的“包路徑”。
  3. 在每個你想成為 Python package 或子包的目錄中放一個__init__.py
  4. 或者,使用mkinit自動生成__init__.py文件的內容。
  5. setup.py / pyproject.toml放在“回購路徑”的根目錄中。
  6. 使用pip install -e. 在開發時以可編輯模式安裝 package。
  7. 使用python -m將模塊名稱作為腳本調用。

希望這可以幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM