兄弟パッケージのインポート


199

兄弟のインポート、さらにはパッケージのドキュメントについての質問を読んでみました みましたが、答えはまだわかりません。

次の構造で:

├── 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モジュールしてコマンドラインから実行するにはですか?

また、sys.path.insertすべてのファイルの醜いハックを回避したいと思います。確かに、これはPythonで実行できますよね?


7
私はすべてのsys.pathハックをスキップして、これまでに投稿された(7年後の)唯一の実際のソリューションを読むことをお勧めします。
2018

1
ところで、ライブラリコードから実行可能コードを分離するという、別の優れたソリューションの余地はまだあります。ほとんどの場合、パッケージ内のスクリプトは最初から実行可能であってはなりません。
Aran-Fey

これは、質問と回答の両方でとても役に立ちます。私はただ知りたいのですが、この場合、「承認された回答」が報奨金を授与されたものと同じではないのはなぜですか。
Indominus

@ Aran-Feyこれは、これらの相対的なインポートエラーに関するQ&Aで過小評価されているリマインダーです。私はずっとハックを探していましたが、問題の解決策を設計する簡単な方法があることを深く知っていました。それがここで読んでいるすべての人にとっての解決策であると言うことは言うまでもありませんが、それは多くの人にとってそうである可能性があるので、良い思い出させるものです。
colorlace

回答:


69

7年後

以下で答えを書いたので、変更sys.pathはまだプライベートスクリプトでうまく機能する簡単で汚れたトリックですが、いくつかの改善がありました

  • パッケージを(virtualenvにあるかどうかに関係なく)インストールすると、必要なものが得られますが、setuptoolsを直接使用する(およびsetup.cfgメタデータを格納するために使用する)よりも、pipを使用して実行することをお勧めします
  • -mフラグを使用するをしてパッケージとして実行することもできます(ただし、作業ディレクトリをインストール可能なパッケージに変換する場合は、少し面倒です)。
  • 具体的には、テストでは、pytestがこの状況でapiパッケージを見つけて、sys.pathハッキングを処理します。

ですから、それはあなたが何をしたいかによります。しかし、あなたの場合、あなたの目標はある時点で適切なパッケージを作成することだと思われるので、pip -eまだ完全ではない場合でもがおそらく最善の策です。

古い答え

他の場所ですでに述べたように、恐ろしい真実は、兄弟モジュールまたは__main__モジュールからの親パッケージからのインポートを許可するために醜いハックをしなければならないということです。この問題は、PEP 366で詳しく説明されています。PEP 3122はより合理的な方法で輸入品を処理しようとしましたが、Guidoはこれを拒否しました。

唯一の使用例は、モジュールのディレクトリ内にたまたま存在するスクリプトを実行しているようです。これは、常にアンチパターンと見なされています。

ここに

しかし、私はこのパターンを定期的に使用しています

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__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の親フォルダー)からの絶対インポートを許可しています。


3
あなたはしていないする必要があなたがあれば使用して、プロジェクトディレクトリから実行-m形式をしたり、パッケージをインストールした場合(ピップと簡単にそれを作るVIRTUALENV)
JFS

2
pytestはどのようにapiパッケージを見つけますか?面白いことに、特にpytestと兄弟パッケージのインポートでこの問題が発生しているため、このスレッドを見つけました。
JuniorIncanter

1
二つ質問があります。1. __package__ = "examples"私の場合、あなたのパターンは機能しないようです。なぜそれを使うのですか?2.どのような状況にあります__name__ == "__main__"が、そうで__package__はありませんNoneか?
actual_panda

@actual_panda設定__packages__は、iirc examples.apiを動作させるなどの絶対パスが必要な場合に役立ちます(ただし、これが最後に実行されてから長い時間がかかります)。
Evpok

165

sys.pathハックにうんざりしていませんか?

sys.path.append利用可能な-hacks はたくさんありますが、手元にある問題を解決する別の方法を見つけました。

概要

  • コードを1つのフォルダーにラップします(例packaged_stuff
  • setuptools.setup()setup.pyを使用する場合は、作成スクリプトを使用します。
  • パッケージを編集可能な状態でピップインストール pip install -e <myproject_folder>
  • 使用してインポート from packaged_stuff.modulename import function_name

セットアップ

開始点は、と呼ばれるフォルダにラップされた、提供されたファイル構造です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.py

テストケースとして、次の./api/api.pyを使用します

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

test_oneを実行してみます。

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

手順

  1. ルートレベルのディレクトリにsetup.pyファイルを作成する

の内容は次のsetup.pyようになります*

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. 仮想環境を使用する

仮想環境に慣れている場合は、仮想環境をアクティブにして、次の手順にスキップしてください。仮想環境の使用は絶対に必須というわけではありませんが、長期に見ると本当に役立ちます(複数のプロジェクトが進行中の場合)。最も基本的な手順は次のとおりです(ルートフォルダーで実行)

  • 仮想環境を作成する
    • 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]
  1. プロジェクトを編集可能な状態でpipインストールする

を使用して最上位パッケージ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
  1. myproject.インポートに追加

myproject.他の方法では機能しないインポートにのみ追加する必要があることに注意してください。setup.py&なしで機能したインポートはpip install引き続き正常に機能します。以下の例を参照してください。


ソリューションをテストする

次に、api.py上でtest_one.py定義した定義と下で定義した定義を使用してソリューションをテストします。

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__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のドキュメントをご覧ください。

**実際には、仮想環境をハードディスクのどこにでも置くことができます。


13
詳細な投稿ありがとうございます。これが私の問題です。私があなたが言ったすべてをし、私がピップフリーズをするなら、私は-e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myproject解決する方法について何か考えがありますか?
Si Mon

2
相対インポートソリューションが機能しないのはなぜですか?私はあなたを信じていますが、Pythonの複雑なシステムを理解しようとしています。
Jared Nielsen、

8
誰かに問題がありModuleNotFoundErrorますか?私は、次の手順を実行するにvirtualenvに「MyProjectと」をインストールしている、と私は解釈したセッションを入力して実行したときにimport myproject私が取得しますかModuleNotFoundError: No module named 'myproject'pip list installed | grep myprojectそれがあることを示している、ディレクトリが正しい、とのverison両方pipとはpython正しいことが検証されています。
SomeKind

2
@ np8さん、うまくいきました。誤ってvenvとosにインストールしました:) pip listパッケージが表示されますが、pip freezeフラグ-eを使用して
Grzegorz Krug

3
相対インポートを機能させる方法を理解するために約2時間費やしましたが、この回答が最終的に実際に賢明なことを行いました。👍👍
グラハム・リー

43

これは、testsフォルダー内のPythonファイルの先頭に挿入する別の方法です。

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

1
+1は本当にシンプルで、完全に機能しました。親クラスをインポートに追加する必要があります(例:api.api、examples.example_two)が、私はそれを優先しています。
エヴァンプライス

10
初心者(私のように)に言及する価値はあると思います。これ..は、テスト/サンプルファイルを含むディレクトリではなく、実行元のディレクトリに関連していることです。私はプロジェクトディレクトリから実行していますが、./代わりに必要でした。これが他の誰かを助けることを願っています。
Joshua Detwiler、2018年

@JoshDetwiler、そうです。私はそれを知りませんでした。ありがとう。
doak

1
これは悪い答えです。パスをハッキングすることは良い習慣ではありません。Pythonの世界でどれだけ使用されているかは不愉快です。この質問の主なポイントの1つは、この種のハックを回避しながらインポートを実行する方法を確認することでした。
jtcotton63

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwiler
vldbnc

31

必要がない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代わりに。

__init__.pyから削除してexamples(Pythonパッケージではありません)、apiがインストールされているvirtualenvで例を実行することもできます。たとえば、適切ながある場合pip install -e .、virtualenvはインプレースapiパッケージをインストールしますsetup.py


@Alexの回答では、「APIの単体テストの場合」と明示的に記載されている段落を除き、テストがAPIテストであるとは想定されていません。
jfs 2016年

残念なことに、ルートディレクトリからの実行で立ち往生していて、PyCharmはその素晴らしい機能のためのファイルをまだ見つけません
mhstnsc

@mhstnsc:不正解です。python -m api.test.test_onevirtualenvがアクティブ化されると、どこからでも実行できるはずです。テストを実行するようにPyCharmを構成できない場合は、新しいStack Overflowの質問をしてみてください(このトピックに関する既存の質問が見つからない場合)。
jfs 2017年

@jfs私は仮想環境パスを逃しましたが、これを任意のディレクトリから実行するためにシバン行以上のものを使用したくありません。それはPyCharmで実行することについてではありません。PyCharmを使用する開発者は、それらが完了していて、どのソリューションでも機能させることができなかった機能をジャンプスルーすることも知っています。
mhstnsc 2017年

適切なシェバング@mhstnsc virtualenvのパイソンのバイナリをポイントし、それを(多くの場合、十分である任意のまともなPythonのIDEはvirtualenvのをサポートする必要があります。。
JFS

9

兄弟/相対インポートハックなしで無関係なプロジェクト間でコードを共有するための意図された方法を確認するのに必要な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

これは依然としてapi親ディレクトリを提供し、「/ ..」連結は必要ありません。sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(file)))) )
カミロ・サンチェス

4

兄弟パッケージのインポートの場合、[sys.path] [2]モジュールの挿入または追加メソッドのいずれかを使用できます。

if __name__ == '__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 __name__ == '__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 __name__ == '__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

__file__グローバルがないClickフレームワークを使用していたため、以下を使用する必要がありました。sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))ただし、現在はどのディレクトリでも機能しています
GammaGames

3

TLDR

この方法では、setuptools、パスハック、追加のコマンドライン引数、またはプロジェクトのすべてのファイルでパッケージの最上位レベルを指定する必要はありません。

あなたがあなたであると呼んでいるものの親ディレクトリにスクリプトを作成し、__main__そこからすべてを実行するだけです。詳細については、読み続けてください。

説明

これは、新しいパスを一緒にハッキングしたり、コマンドライン引数を追加したり、兄弟を認識するために各プログラムにコードを追加したりすることなく実行できます。

前に述べたと私が信じているようにこれが失敗する理由は、呼び出されるプログラムが__name__として設定されているため__main__です。これが発生すると、呼び出されるスクリプトは、パッケージの最上位にあることを受け入れ、兄弟ディレクトリ内のスクリプトの認識を拒否します。

しかし、ディレクトリのトップレベルの下のすべてがまだ認識して何かをトップレベルの下で。この手段ONLYあなたがお互いを利用/認識するディレクトリを兄弟でファイルを取得するためにしなければならない事は彼らの親ディレクトリ内のスクリプトからそれらを呼び出すことです。

概念実証 次の構造を持つディレクトリ:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py 次のコードが含まれています。

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.pyには以下が含まれます:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

そして、sib2 / callsib.pyには以下が含まれます:

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

この例を再現すると、を介してMain.py呼び出されたsib2/callsib.py 場合でも、呼び出しによって「Got Called」が定義されたとおりに印刷されることがわかります。ただし、(インポートに適切な変更を加えた後で)直接呼び出すと、例外がスローされます。親ディレクトリのスクリプトによって呼び出されたときに機能しましたが、パッケージの最上位にあると思われる場合は機能しません。sib2/callsib.pysib1/call.pysib1/call.py


2

私がこれをどのように処理したかを示すためにサンプルプロジェクトを作成しました。これは確かに、上記の別のsys.pathハックです。Python Sibling Import Exampleは、次のものに依存しています。

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

これは、作業ディレクトリがPythonプロジェクトのルートにある限り、かなり効果があるようです。誰かがこれを実際の実稼働環境にデプロイした場合、それが同様に機能するかどうかを聞くのは素晴らしいことです。


これは、スクリプトの親ディレクトリから実行している場合にのみ機能します
Evpok

1

関連するコードでimportステートメントがどのように記述されているかを確認する必要があります。examples/example_one.py次のインポートステートメントを使用する場合:

import api.api

...次に、プロジェクトのルートディレクトリがシステムパスにあると想定しています。

ハックせずにこれをサポートする最も簡単な方法は(次のように)、最上位ディレクトリからサンプルを実行することです。

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

Python 2.7.1では、次のようになります$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api。私も同じimport api.apiです。
zachwill

私の答えを更新しました... 現在のディレクトリをインポートパスに追加する必要あります、それを回避する方法はありません。
AJ。

1

念のためにEclipseをPyDevはを使用して誰かがここで終わる:あなたは使用して、フォルダの外部ライブラリとして兄弟の親パス(したがって、呼び出しモジュールの親)を追加することができますプロジェクト- >プロパティをして設定する外部ライブラリを左側のメニューの下にPyDevは、PYTHONPATH。その後、兄弟などからインポートできますfrom sibling import some_class


-3

まず、モジュール自体と同じ名前のファイルを作成しないでください。他のインポートが壊れる可能性があります。

ファイルをインポートすると、インタープリターはまず現在のディレクトリを確認してから、グローバルディレクトリを検索します。

内部examplesまたはtestsあなたが呼び出すことができます:

from ..api import api

私は、Python 2.7.1と次を得る:Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
zachwill

2
ああ、それからあなたは__init__.pyトップレベルのディレクトリにファイルを追加するべきです。そうでない場合、Pythonはそれをモジュールとして処理できません

8
動作しません。問題は、親フォルダーがパッケージではないことで__name____main__なく、モジュールのがの代わりにあるためpackage.module、Pythonが親パッケージを認識できないため.、何もポイントしません。
Evpok 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.