web-dev-qa-db-ja.com

複数のpythonファイルをパッケージのように振る舞わずに単一のモジュールに整理する方法は?

___init__.py_を使用して複数のファイルをモジュールに整理する方法はありますか?

理由:モジュールは名前空間のレイヤーが少ないため、パッケージよりも使いやすいです。

通常、パッケージを作成します。問題はパッケージにあり、「パッケージのインポート」は空の名前空間を提供します。その後、ユーザーは「from thepackage import *」(しかめ面)を使用するか、何が含まれているかを正確に把握して、使用可能な名前空間に手動でプルする必要があります。

私が欲しいのは、ユーザーが「パッケージをインポート」し、このように見えるニースのきれいな名前空間を持ち、使用するプロジェクトに関連する関数とクラスを公開することです。

_current_module
\
  doit_tools/
  \
   - (class) _hidden_resource_pool
   - (class) JobInfo
   - (class) CachedLookup
   - (class) ThreadedWorker
   - (Fn) util_a
   - (Fn) util_b
   - (Fn) gather_stuff
   - (Fn) analyze_stuff
_

メンテナーの仕事は、異なる名前のファイルで同じ名前を定義するのを避けることです。これは、私のようにプロジェクトが小さい場合は簡単です。

人々が_from doit_stuff import JobInfo_を実行し、クラスを含むモジュールではなく、クラスを取得できるようにすることもまた素晴らしいことです。

すべてのコードが1つの巨大なファイルにある場合、これは簡単ですが、物事が大きくなり始めたら整理したいです。私がディスク上に持っているものは、次のようなものです。

_place_in_my_python_path/
  doit_tools/
    __init__.py
    JobInfo.py
      - class JobInfo:
    NetworkAccessors.py
      - class _hidden_resource_pool:
      - class CachedLookup:
      - class ThreadedWorker:
    utility_functions.py
      - def util_a()
      - def util_b()
    data_functions.py
      - def gather_stuff()
      - def analyze_stuff()
_

私はそれらを分離するだけなので、私のファイルは巨大でナビゲートできません。それらはすべて関連していますが、誰か(可能であれば)はすべてをインポートせずに自分でクラスを使用したい場合があります。

私はさまざまなスレッドでいくつかの提案を読んでいますが、これを行う方法について見つけることができる提案ごとに何が起こるのですか?

もし私が ___init__.py_を使用しないでください、Pythonはsys.pathからフォルダーに降りないため、何もインポートできません。

もし私が 空白の___init__.py_を使用します、_import doit_tools_のとき、それは何も入っていない空の名前空間です。ファイルがインポートされないため、使用がより困難になります。

もし私が ___all___のサブモジュールをリストします、(眉をひそめた?)_from thing import *_構文を使用できますが、すべてのクラスは再び不必要な名前空間の壁の背後にあります。ユーザーは、(1)_from x import *_の代わりに_import x_を使用する必要があることを知っておく必要があります。(2)線幅スタイルの制約に合理的に従うまで、クラスを手動で再シャッフルします。

もし私が _from thatfile import X_ステートメントを___init__.py_に追加します、私は近くなりますが、名前空間の競合(?)と、そこにいたくないもののための余分な名前空間があります。以下の例では、次のことがわかります。

  1. JobInfoクラスは、名前が同じであるため、JobInfoという名前のモジュールオブジェクトを上書きしました。 JobInfoのタイプは_<class 'doit_tools.JobInfo.JobInfo'>_であるため、何らかの方法でPythonがこれを理解できます。doit_tools.JobInfoはクラスですが、doit_tools.JobInfo.JobInfoは同じクラスです...これはもつれたと非常に悪いようですが、何も壊していないようです。)
  2. 各ファイル名はdoit_tools名前空間に格納されているため、モジュールの内容を誰かが見ている場合、見づらくなります。 doit_tools.utility_functions.pyには、新しい名前空間を定義するのではなく、いくつかのコードを保持する必要があります。

_current_module
\
  doit_tools/
  \
   - (module) JobInfo
      \
       - (class) JobInfo
   - (class) JobInfo
   - (module) NetworkAccessors
      \
       - (class) CachedLookup
       - (class) ThreadedWorker
   - (class) CachedLookup
   - (class) ThreadedWorker
   - (module) utility_functions
      \
       - (Fn) util_a
       - (Fn) util_b
   - (Fn) util_a
   - (Fn) util_b
   - (module) data_functions
      \
       - (Fn) gather_stuff
       - (Fn) analyze_stuff
   - (Fn) gather_stuff
   - (Fn) analyze_stuff
_

また、データ抽象化クラスだけをインポートする人は、「from doit_tools import JobInfo」を実行するときに予想とは異なるものを取得します。

_current_namespace
\
 JobInfo (module)
  \
   -JobInfo (class)

instead of:

current_namespace
\
 - JobInfo (class)
_

だから、これはPythonコードを整理するのに間違った方法ですか?そうでない場合、関連するコードを分割し、モジュールのような方法でそれを収集する正しい方法は何ですか?

たぶん最良のケースのシナリオは、「doit_tools import JobInfoから」を行うことは、パッケージを使用している人にとって少し混乱することでしょうか?

多分python 'api'と呼ばれるファイルなので、コードを使用している人は次のようにしますか?:

_import doit_tools.api
from doit_tools.api import JobInfo
_

===========================================

コメントへの応答の例:

pythonパスにあるフォルダー 'foo'内の次のパッケージの内容を取得します。

_foo/__init__.py_

___all__ = ['doit','dataholder','getSomeStuff','hold_more_data','SpecialCase']
from another_class import doit
from another_class import dataholder
from descriptive_name import getSomeStuff
from descriptive_name import hold_more_data
from specialcase import SpecialCase
_

_foo/specialcase.py_

_class SpecialCase:
    pass
_

_foo/more.py_

_def getSomeStuff():
    pass

class hold_more_data(object):
    pass
_

_foo/stuff.py_

_def doit():
    print "I'm a function."

class dataholder(object):
    pass
_

これを行う:

_>>> import foo
>>> for thing in dir(foo): print thing
... 
SpecialCase
__builtins__
__doc__
__file__
__name__
__package__
__path__
another_class
dataholder
descriptive_name
doit
getSomeStuff
hold_more_data
specialcase
_

_another_class_と_descriptive_name_は、物を散らかしているだけでなく、たとえば名前空間の下のdoit()。

Data.pyという名前のファイル内にDataという名前のクラスがある場合、「from Data import Data」を実行すると、DataはモジュールData内にある現在のネームスペースのクラスであるため、ネームスペースの競合が発生します。現在の名前空間。 (ただし、Pythonはこれを処理できるようです。)

35
Brian

並べ替えることはできますが、それはあまり良い考えではなく、Pythonモジュール/パッケージが機能するはずです。適切な名前を__init__.pyにインポートすることにより、パッケージの名前空間でアクセス可能にすることができます。モジュール名を削除すると、アクセスできなくなります(削除する理由については、 この質問 を参照してください)。次のようなもの(__init__.py内):

from another_class import doit
from another_class import dataholder
from descriptive_name import getSomeStuff
from descriptive_name import hold_more_data
del another_class, descriptive_name
__all__ = ['doit', 'dataholder', 'getSomeStuff', 'hold_more_data']

ただし、これはその後のimport package.another_classへの試行を中断します。一般に、package.moduleからは、そのモジュールへのインポート可能な参照としてpackage.moduleをアクセス可能にせずに何もインポートできません(__all__を使用すると、from package import moduleをブロックできます)。

より一般的には、コードをクラス/関数ごとに分割することにより、Pythonパッケージ/モジュールシステムに対して作業します。Pythonモジュールは、通常、便宜上、最上位のパッケージ名前空間にサブモジュールコンポーネントを直接インポートすることは珍しいことではありませんが、逆に---サブモジュールを非表示にしてそのコンテンツへのアクセスを許可しようとしています onlyトップレベルのパッケージ名前空間を介して---は問題を引き起こします。また、モジュールのパッケージ名前空間を「クレンジング」しようとしても何も得られません。モジュールはパッケージ名前空間にあると想定されており、そこに属します。

14
BrenBarn

__all__ = ['names', 'that', 'are', 'public']__init__.pyを定義します。例:

__all__ = ['foo']

from ._subpackage import foo

実際の例: numpy/__init__.py


Pythonパッケージの動作について誤解があります。

__init__.pyを使用しない場合は、Pythonがsys.pathからフォルダーに降りないため、何もインポートできません。

Pythonパッケージを含むディレクトリをマークするには、Python 3.3より古いPythonバージョンの__init__.pyファイルが必要です。

空の__init__.pyを使用する場合、doit_toolsをインポートすると、何も含まれていない空の名前空間になります。ファイルがインポートされないため、使用がより困難になります。

インポートを妨げません:

from doit_tools import your_module

期待どおりに動作します。

サブモジュールを__all__にリストすると、(眉をひそめた?)from thing import *構文を使用できますが、すべてのクラスが不必要な名前空間の障壁の背後に再びあります。ユーザーは、(1)from x import *の代わりにimport xを使用する必要があることを知っておく必要があります。(2)線幅スタイルの制約に従うことができるようになるまで、クラスを手動でシャッフルします。

(1)ユーザーは(ほとんどの場合)インタラクティブなPythonシェルの外でfrom your_package import *を使用しないでくださいnot

(2)()を使用して長いインポート行を分割できます。

from package import (function1, Class1, Class2, ..snip many other names..,
                     ClassN)

from thatfile import Xステートメントを__init__.pyに追加すると、近づきますが、名前空間の競合(?)と、そこに入れたくないもののための余分な名前空間があります。

名前空間の競合(同じ名前の異なるオブジェクト)を解決するのはあなた次第です。名前は、整数、文字列、パッケージ、モジュール、クラス、関数などの任意のオブジェクトを参照できます。Pythonは、どのオブジェクトを好むかを知ることができず、一部のオブジェクトを無視することはできません。他のすべての場合の名前バインディングの使用に関して、この特定の場合の名前バインディング。

名前を非公開としてマークするには、_をプレフィックスとして付けることができます(例:package/_nonpublic_module.py)。

3
jfs

パッケージのサブ構造を非表示にする完全に正当な理由があります(デバッグ時だけでなく)。その中にはconvenienceefficiencyがあります。パッケージを使用してラピッドプロトタイプを作成しようとすると、特定の関数またはクラスの正確なサブモジュールが何であるかをまったく役に立たない情報を検索するために思考の流れを中断する必要が非常に面倒です。

パッケージの最上位レベルですべてが利用可能になると、イディオムは次のようになります。

python -c 'import pkg; help(pkg)'

単なるモジュール名だけでなく、全体のヘルプを表示します。

製品コードのサブモジュールのインポートをいつでもオフにしたり、開発後にパッケージモジュールをクリーンアップしたりできます。

以下は、私がこれまでに思いついた最良の方法です。有効なエラーを抑制しないようにしながら、利便性を最大化します。 doctestドキュメントの完全なソース も参照してください。


エラーが発生しやすい重複を避けるために、インポートするパッケージ名とサブモジュールを定義します。

_package_ = 'flat_export'
_modules_ = ['sub1', 'sub2', 'sub3']

可能な場合は相対インポートを使用します(これは必須です。is_importing_packageを参照):

_loaded = False
if is_importing_package(_package_, locals()):
    for _module in _modules_:
        exec ('from .' + _module + ' import *')
    _loaded = True
    del(_module)

__all__を含むパッケージをインポートしてみてください。
これは、検索パスにパッケージを含むスクリプトとしてモジュールファイルを実行するときに発生します(例:python flat_export/__init__.py

if not _loaded:
    try:
        exec('from ' + _package_ + ' import *')
        exec('from ' + _package_ + ' import __all__')
        _loaded = True
    except (ImportError):
        pass

最後の手段として、サブモジュールを直接インポートしてみてください。
パッケージパスを検索パスに含めずにパッケージディレクトリ内でスクリプトとしてモジュールファイルを実行すると発生します(例:cd flat_export; python __init__.py)。

if not _loaded:
    for _module in _modules_:
        exec('from ' + _module + ' import *')
    del(_module)

以前にインポートされていない限り、__all__(モジュールを除外)を構築します。

if not __all__:
    _module_type = type(__import__('sys'))
    for _sym, _val in sorted(locals().items()):
        if not _sym.startswith('_') and not isinstance(_val, _module_type) :
            __all__.append(_sym)
    del(_sym)
    del(_val)
    del(_module_type)

関数is_importing_packageは次のとおりです。

def is_importing_package(_package_, locals_, dummy_name=None):
    """:returns: True, if relative package imports are working.

    :param _package_: the package name (unfortunately, __package__
      does not work, since it is None, when loading ``:(``).
    :param locals_: module local variables for auto-removing function
      after use.
    :param dummy_name: dummy module name (default: 'dummy').

    Tries to do a relative import from an empty module `.dummy`. This
    avoids any secondary errors, other than::

        ValueError: Attempted relative import in non-package
    """

    success = False
    if _package_:
        import sys
        dummy_name = dummy_name or 'dummy'
        dummy_module = _package_ + '.' + dummy_name
        if not dummy_module in sys.modules:
            import imp
            sys.modules[dummy_module] = imp.new_module(dummy_module)
        try:
            exec('from .' + dummy_name + ' import *')
            success = True
        except:
            pass
    if not 'sphinx.ext.autodoc' in __import__('sys').modules:
        del(locals_['is_importing_package'])
    return success
1
wolfmanx