兄弟のインポートや パッケージドキュメント についての質問を読んでみましたが、まだ答えが見つかりません。
次の構造で:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
examples
およびtests
ディレクトリ内のスクリプトをapi
モジュールからインポートし、コマンドラインから実行するにはどうすればよいですか?
また、すべてのファイルのfileいsys.path.insert
ハックを回避したいと思います。確かにこれはPythonで行うことができますよね?
以下の回答を書いたので、sys.path
を変更することは、プライベートスクリプトでうまく機能する手っ取り早い方法ですが、いくつかの改善があります。
setup.cfg
を使用してメタデータを保存するのではなく、pipを使用することをお勧めします) )-m
フラグを使用 およびパッケージとして実行することも機能します(ただし、作業ディレクトリをインストール可能なパッケージに変換する場合は少し厄介になります)。sys.path
ハックを処理しますだからそれは本当にあなたが何をしたいかに依存します。しかし、あなたの場合、あなたの目標はある時点で適切なパッケージを作成することであると思われるため、pip -e
を介してインストールすることは、まだ完全ではない場合でもおそらく最善の策です。
すでに別の場所で述べたように、ひどい真実は、兄弟モジュールからのインポートまたは__main__
モジュールからの親パッケージを許可するためにいハックをしなければならないということです。問題は PEP 366 で詳しく説明されています。 PEP 3122 より合理的な方法で輸入を処理しようとしましたが、グイドはそれを拒否しました
唯一のユースケースは、たまたまモジュールのディレクトリ内にあるスクリプトを実行することであるようです。これは常にアンチパターンと見なされてきました。
( ここ )
しかし、私はこのパターンを定期的に使用しています
# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __== "__main__" and __package__ is None:
from sys import path
from os.path import dirname as dir
path.append(dir(path[0]))
__package__ = "examples"
import api
ここで、path[0]
は実行中のスクリプトの親フォルダーであり、dir(path[0])
は最上位フォルダーです。
ただし、これで相対インポートを使用することはできませんが、最上位レベル(例ではapi
の親フォルダー)からの絶対インポートが可能です。
利用可能なsys.path.append
-hacksはたくさんありますが、問題を解決する別の方法を見つけました: setuptools 。これでうまく機能しないEdgeケースがあるかどうかはわかりません。以下は、Python 3.6.5(Anaconda、conda 4.5.1)、Windows 10マシンでテストされています。
開始点は、myproject
というフォルダーにラップされた、指定したファイル構造です。
.
└── myproject
├── api
│ ├── api_key.py
│ ├── api.py
│ └── __init__.py
├── examples
│ ├── example_one.py
│ ├── example_two.py
│ └── __init__.py
├── LICENCE.md
├── README.md
└── tests
├── __init__.py
└── test_one.py
.
をルートフォルダーと呼びます。この例では、C:\tmp\test_imports\
にあります。
テストケースとして、次の./api/api.pyを使用してみましょう。
def function_from_api():
return 'I am the return value from api.api!'
from api.api import function_from_api
def test_function():
print(function_from_api())
if __== '__main__':
test_function()
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
File ".\myproject\tests\test_one.py", line 1, in <module>
from api.api import function_from_api
ModuleNotFoundError: No module named 'api'
from ..api.api import function_from_api
を使用すると、
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
File ".\tests\test_one.py", line 1, in <module>
from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package
setup.py
の内容は*
from setuptools import setup, find_packages
setup(name='myproject', version='1.0', packages=find_packages())
仮想環境に精通している場合は、アクティブにし、次の手順にスキップします。仮想環境の使用は絶対に必要ですが、それらは本当に長期的にあなたを助けます(複数のプロジェクトがある場合)進行中..)。最も基本的な手順は次のとおりです(ルートフォルダーで実行)
python -m venv venv
source ./venv/bin/activate
(Linux、macOS)または./venv/Scripts/activate
(Win)詳細については、「python virtual env tutorial」などをGoogleで検索してください。おそらく、作成、アクティブ化、非アクティブ化以外のコマンドは必要ありません。
仮想環境を作成してアクティブにすると、コンソールには括弧内に仮想環境の名前が表示されます。
PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
フォルダーツリーは次のようになります**
.
├── myproject
│ ├── api
│ │ ├── api_key.py
│ │ ├── api.py
│ │ └── __init__.py
│ ├── examples
│ │ ├── example_one.py
│ │ ├── example_two.py
│ │ └── __init__.py
│ ├── LICENCE.md
│ ├── README.md
│ └── tests
│ ├── __init__.py
│ └── test_one.py
├── setup.py
└── venv
├── Include
├── Lib
├── pyvenv.cfg
└── Scripts [87 entries exceeds filelimit, not opening dir]
myproject
を使用して、最上位パッケージpip
をインストールします。トリックは、インストール時に-e
フラグを使用することです。この方法で、編集可能な状態でインストールされ、.pyファイルに対して行われたすべての編集は、インストールされたパッケージに自動的に含まれます。
ルートディレクトリで、実行します
pip install -e .
(ドットに注意してください。「現在のディレクトリ」の略です)
pip freeze
を使用してインストールされていることも確認できます
(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
myproject.
をインポートに追加しますmyproject.
は、それ以外では機能しないインポートにのみ追加する必要があることに注意してください。 setup.py
およびpip install
なしで機能したインポートは、引き続き正常に機能します。以下の例を参照してください。
ここで、上記で定義したapi.py
と、以下で定義したtest_one.py
を使用してソリューションをテストしましょう。
from myproject.api.api import function_from_api
def test_function():
print(function_from_api())
if __== '__main__':
test_function()
(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!
*詳細なsetup.pyの例については、 setuptools docs を参照してください。
**実際には、ハードディスクのどこにでも仮想環境を置くことができます。
tests
フォルダーのPythonファイルの先頭に挿入する別の代替方法を次に示します。
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
必要でない限り、sys.path
をハッキングする必要はありません。この場合は必要ありません。つかいます:
import api.api_key # in tests, examples
プロジェクトディレクトリから実行します:python -m tests.test_one
。
おそらくtests
(APIのユニットテストの場合)をapi
内に移動し、python -m api.test
を実行してすべてのテストを実行する(__main__.py
があると仮定)またはpython -m api.test.test_one
に移動する必要があります代わりにtest_one
を実行します。
examples
から__init__.py
(Pythonパッケージではない)を削除し、api
がインストールされているvirtualenvでサンプルを実行することもできます(例:pip install -e .
適切なsetup.py
がある場合、virtualenvでapi
パッケージをインプレースでインストールします。
兄弟/相対的なインポートハックなしで無関係なプロジェクト間でコードを共有する意図された方法を見るために必要なPythonologyの理解がまだありません。その日まで、これが私の解決策です。 examples
またはtests
を..\api
からインポートするには、次のようになります。
import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key
兄弟パッケージのインポートでは、insertまたはappendのいずれかを使用できます[sys.path] [2]モジュールのメソッド:
if __== '__main__' and if __package__ is None:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
import api
これは、次のようにスクリプトを起動する場合に機能します。
python examples/example_one.py
python tests/test_one.py
一方、相対インポートも使用できます。
if __== '__main__' and if __package__ is not None:
import ..api.api
この場合、スクリプトを '-m'引数 で起動する必要があります(この場合、 '。py'を指定しないでください。 拡張子):
python -m packageName.examples.example_one
python -m packageName.tests.test_one
もちろん、2つのアプローチを組み合わせて、スクリプトがどのように呼び出されても機能するようにすることができます。
if __== '__main__':
if __package__ is None:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
import api
else:
import ..api.api
TLDR
この方法では、setuptools、パスハック、追加のコマンドライン引数、またはプロジェクトのすべての単一ファイルでパッケージのトップレベルを指定する必要はありません。
__main__
になるように呼び出しているものの親ディレクトリにスクリプトを作成し、そこからすべてを実行します。詳細については、読み続けてください。
説明
これは、新しいパスを一緒にハッキングしたり、追加のコマンドライン引数を追加したり、兄弟を認識するために各プログラムにコードを追加したりすることなく実現できます。
前に述べたように、これが失敗する理由は、呼び出されるプログラムの__name__
が__main__
に設定されているためです。これが発生すると、呼び出されるスクリプトはパッケージの最上位にあることを受け入れ、兄弟ディレクトリ内のスクリプトを認識しません。
ただし、ディレクトリの最上位の下にあるものはすべて、最上位の下にあるANYTHING ELSEを認識します。つまり、兄弟ディレクトリのファイルを取得して相互に認識/利用するためにのみを実行する必要があるのは、親ディレクトリのスクリプトからファイルを呼び出すことです。
概念実証次の構造のディレクトリ内:
.
|__Main.py
|
|__Siblings
|
|___sib1
| |
| |__call.py
|
|___sib2
|
|__callsib.py
Main.py
には次のコードが含まれます。
import sib1.call as call
def main():
call.Call()
if __== '__main__':
main()
sib1/call.pyの内容:
import sib2.callsib as callsib
def Call():
callsib.CallSib()
if __== '__main__':
Call()
sib2/callsib.pyには以下が含まれます。
def CallSib():
print("Got Called")
if __== '__main__':
CallSib()
この例を再現すると、Main.py
を介してsib2/callsib.py
が呼び出された場合でも、sib2/callsib.py
を呼び出すとsib1/call.py
で定義されているように「Got Called」が出力されます。ただし、sib1/call.py
を直接呼び出すと(インポートに適切な変更を加えた後)、例外がスローされます。親ディレクトリのスクリプトから呼び出されたときは動作していましたが、パッケージの最上位にあると考えている場合は動作しません。
EclipseでPydevを使用している人がここにいる場合に備えて:Project-> Propertiesを使用して兄弟の親パス(および呼び出しモジュールの親)を外部ライブラリフォルダーとして追加できます外部ライブラリ左メニューPydev-PYTHONPATHの下。その後、兄弟からインポートできます。 g。 from sibling import some_class
。
インポートステートメントが関連するコードでどのように記述されているかを確認する必要があります。 examples/example_one.py
が次のインポートステートメントを使用する場合:
import api.api
...次に、プロジェクトのルートディレクトリがシステムパスにあると想定します。
ハックなしでこれをサポートする最も簡単な方法は、次のように最上位ディレクトリから例を実行することです。
PYTHONPATH=$PYTHONPATH:. python examples/example_one.py
これをどのように処理するかを示すためにサンプルプロジェクトを作成しました。これは、上記のsys.pathハックです。 Python兄弟インポートの例 、これは次のものに依存します。
if __== '__main__': import os import sys sys.path.append(os.getcwd())
作業ディレクトリがPythonプロジェクトのルートにある限り、これは非常に効果的であるようです。誰かが実際の実稼働環境でこれを展開する場合、それがそこでも動作するかどうかを聞くのは素晴らしいことです。