web-dev-qa-db-ja.com

パッケージ内の各ファイルを引き続き個別に実行できるように、複数のパッケージを含むpythonプロジェクトをどのように編成しますか?

TL; DR

以下は、最初の図(下記)で説明されているように設定されたリポジトリの例です。 https://github.com/Poddster/package_problems

プロジェクト構成の点で2番目の図のように見せても、次のコマンドを実行できる場合は、質問に回答しています。

$ git clone https://github.com/Poddster/package_problems.git
$ cd package_problems
<do your magic here>

$ nosetests

$ ./my_tool/my_tool.py
$ ./my_tool/t.py
$ ./my_tool/d.py

 (or for the above commands, $ cd ./my_tool/ && ./my_tool.py is also acceptable)

または、関連するファイル(「パッケージ」)をグループ化し、すべてのファイルを個別に実行し、同じパッケージ内の他のファイルにファイルをインポートし、パッケージ/ファイルを他のパッケージのファイルにインポートできるようにする別のプロジェクト構造を教えてください。


現在の状況

私はpythonファイルの束を持っています。それらのほとんどは、コマンドラインから呼び出すことができるときに便利です。つまり、すべてがargparseおよびif __name__ == "__main__"を使用して便利なことを行います。

現在、私はこのディレクトリ構造を持っており、すべてが正常に動作しています:

.
├── config.txt
├── docs/
│   ├── ...
├── my_tool.py
├── a.py
├── b.py
├── c.py
├── d.py
├── e.py
├── README.md
├── tests
│   ├── __init__.py
│   ├── a.py
│   ├── b.py
│   ├── c.py
│   ├── d.py
│   └── e.py
└── resources
    ├── ...

一部のスクリプトは、他のスクリプトからのimport処理を実行するためのものです。しかし、スクリプトは単なるライブラリではなく、すべて呼び出し可能です。例えば./my_tool.py./a.by./b.py./c.pyなどを呼び出すことができ、それらはユーザーにとって有用なことを行います。

「my_tool.py」は、他のすべてのスクリプトを活用するメインスクリプトです。

起こりたいこと

ただし、プロジェクトの編成方法を変更したいと思います。プロジェクト自体は、ユーザーが使用できるプログラム全体を表しており、そのように配布されますが、その一部は後で別のプロジェクトでも役立つため、現在のファイルをパッケージにカプセル化したいと思います。近い将来、この同じプロジェクトに他のパッケージも追加する予定です。

これを容易にするために、プロジェクトを次のようなものに再編成することにしました。

.
├── config.txt
├── docs/
│   ├── ...
├── my_tool
│   ├── __init__.py
│   ├── my_tool.py
│   ├── a.py
│   ├── b.py
│   ├── c.py
│   ├── d.py
│   ├── e.py
│   └── tests
│       ├── __init__.py
│       ├── a.py
│       ├── b.py
│       ├── c.py
│       ├── d.py
│       └── e.py
├── package2
│   ├── __init__.py
│   ├── my_second_package.py
|   ├── ...
├── README.md
└── resources
    ├── ...

ただし、次の基準を満たすプロジェクト組織はわかりません。

  1. すべてのスクリプトはコマンドラインから呼び出すことができます(my_tool\a.pyまたはcd my_tool && a.pyとして)
  2. テストは実際に実行されます:)
  3. Package2のファイルはimport my_toolを実行できます

主な問題は、パッケージとテストで使用されるインポート文にあります。

現在、テストを含むすべてのパッケージは単にimport <module>を実行するだけで正しく解決されます。しかし、周りの物事を揺さぶるときはそれは機能しません。

Py2.7のサポートは必須であるため、すべてのファイルの先頭にfrom __future__ import absolute_import, ...があることに注意してください。

私が試したことと悲惨な結果

1

上記のようにファイルを移動しますが、現在のようにすべてのインポートステートメントをそのままにします。

  1. $ ./my_tool/*.pyは機能し、すべて正しく動作します
  2. $ nosetests最上位ディレクトリから実行しても機能しません。テストはパッケージスクリプトのインポートに失敗します。
  3. pycharmは、これらのファイルを編集するときにインポート文を赤で強調表示します:(

2

次に、テストスクリプトを次のように変更した場合:

from my_tool import x
  1. $ ./my_tool/*.pyは引き続き機能し、すべて正しく動作します
  2. $ nosetests最上位ディレクトリから実行しても機能しません。その後、テストは正しいスクリプトをインポートできますが、テストスクリプトがそれらをインポートすると、スクリプト自体のインポートは失敗します。
  3. pycharmはまだメインスクリプトのインポートステートメントを赤で強調表示しています:(

同じ構造を維持し、everythingfrom my_tool importに変更すると、次のようになります。

  1. $ ./my_tool/*.py結果はImportErrorsになります
  2. $ nosetestsはすべて正常に実行されます。
  3. pycharmは何も文句を言わない

例えばの1 .:

Traceback (most recent call last):
  File "./my_tool/a.py", line 34, in <module>
    from my_tool import b
ImportError: cannot import name b

4

私はfrom . import xも試しましたが、スクリプトを直接実行するためにValueError: Attempted relative import in non-packageで終わるだけです。

他のいくつかを見るSO答え:

python -m pkg.tests.core_testをそのまま使用することはできません

a)main。pyがありません。私は持っていると思いますか?
b)メインだけでなく、すべてのスクリプトを実行できるようにしたいですか?

私はもう試した:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

しかし、それは助けにはなりませんでした。

私も試しました:

__package__ = "my_tool"
from . import b

しかし受け取った:

SystemError: Parent module 'loading_tool' not loaded, cannot perform relative import

import my_toolの前にfrom . import bを追加すると、ImportError: cannot import name bで終了します

修正?

このすべてを機能させるための魔法の呪文とディレクトリレイアウトの正しいセットは何ですか?

31
Pod

目的の構成に移動すると、my_toolに固有のモジュールをロードするために使用している絶対インポートは機能しなくなります。

my_toolサブディレクトリを作成してファイルをそこに移動した後、3つの変更が必要です。

  1. my_tool/__init__.pyを作成します。 (あなたはすでにこれをしているようですが、私は完全性のためにそれを述べたかったです。)

  2. my_toolの直下のファイル:importステートメントを変更して、現在のパッケージからモジュールをロードします。したがって、my_tool.pyの変更:

    import c
    import d
    import k
    import s
    

    に:

    from . import c
    from . import d
    from . import k
    from . import s
    

    他のすべてのファイルにも同様の変更を加える必要があります。 (__package__を設定してから相対インポートを実行したが、__package__の設定は不要であると述べました。)

  3. my_tool/testsにあるファイルで、テストするコードをインポートするimportステートメントを、階層内の1つのパッケージからロードする相対インポートに変更します。したがって、test_my_tool.pyの変更:

    import my_tool
    

    に:

    from .. import my_tool
    

    他のすべてのテストファイルについても同様です。

上記の変更により、モジュールを直接実行できます。

$ python -m my_tool.my_tool
C!
D!
F!
V!
K!
T!
S!
my_tool!
my_tool main!
|main tool!||detected||tar edit!||installed||keys||LOL||ssl connect||parse ASN.1||config|

$ python -m my_tool.k
F!
V!
K!
K main!
|keys||LOL||ssl connect||parse ASN.1|

テストを実行できます:

$ nosetests 
........
----------------------------------------------------------------------
Ran 8 tests in 0.006s

OK

Python 2.7 and Python 3。


my_toolの下のさまざまなモジュールを直接実行可能にするのではなく、適切なsetup.pyファイルを使用してエントリポイントを宣言し、パッケージのインストール時にこれらのエントリポイントをsetup.pyで作成することをお勧めします。このコードを配布するつもりなので、とにかく正式にパッケージ化するにはsetup.pyを使用する必要があります。

  1. コマンドラインから呼び出すことができるモジュールを変更して、以下の代わりにmy_tool/my_tool.pyを例にとります。

    if __name__ == "__main__":
        print("my_tool main!")
        print(do_something())
    

    あなたが持っている:

    def main():
        print("my_tool main!")
        print(do_something())
    
    if __name__ == "__main__":
        main()
    
  2. 適切なsetup.pyを含むentry_pointsファイルを作成します。例えば:

    from setuptools import setup, find_packages
    
    setup(
        name="my_tool",
        version="0.1.0",
        packages=find_packages(),
        entry_points={
            'console_scripts': [
                'my_tool = my_tool.my_tool:main'
            ],
        },
        author="",
        author_email="",
        description="Does stuff.",
        license="MIT",
        keywords=[],
        url="",
        classifiers=[
        ],
    )
    

    上記のファイルは、モジュールsetup.pymainメソッドを呼び出すmy_toolという名前のスクリプトを作成するようmy_tool.my_toolに指示しています。私のシステムでは、パッケージがインストールされると、/usr/local/bin/my_toolにあるスクリプトがあり、my_tool.my_toolmainメソッドを呼び出します。上で示したpython -m my_tool.my_toolの実行と同じ出力が生成されます。

14
Louis

ポイント1

動作していると思うので、コメントしません。

ポイント2

私は常にmy_toolと同じレベルでテストを使用しましたが、その下ではありませんが、これを各テストファイルの最上部で実行すると機能します(my_toolまたは他のpyファイルを同じディレクトリにインポートする前に)

import os
import sys

sys.path.insert(0, os.path.abspath(__file__).rsplit(os.sep, 2)[0])

ポイント3

My_second_package.pyの上部でこれを行います(my_toolをインポートする前に)

import os
import sys

sys.path.insert(0,
                os.path.abspath(__file__).rsplit(os.sep, 2)[0] + os.sep
                + 'my_tool')

宜しくお願いします、

JM

1
jmatos

Nosetestを標準的な方法で操作できるようにしながら、コマンドラインから実行し、ライブラリのように動作させるには、インポートでダブルアップアプローチを実行する必要があると思います。

たとえば、Pythonファイルには以下が必要です。

try:
    import f
except ImportError:
    import tools.f as f

すべてのテストケースが機能しているgithubからPRを実行しました。

https://github.com/Poddster/package_problems/pull/1

編集:__init__.pyのインポートを忘れて、他のパッケージで適切に使用できるようになり、追加されました。これで、次のことができるはずです。

import tools
tools.c.do_something()
0
CasualDemon